Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 13 Nov 2014 15:47:16 -0500
changeset 215626 a05b5362429f96bd69cd8bd34d50d1eaebbdc97f
parent 215560 30276610fd2999ccf0dfe07b826f6b3d50b8a98e (current diff)
parent 215625 7f0d92595432f9c0a8e83c21f7a7ed4bd43d2b9d (diff)
child 215627 5dfdebf9cc796e449c2a512a9393df3657fb8b6c
push id51796
push userryanvm@gmail.com
push dateThu, 13 Nov 2014 20:47:14 +0000
treeherdermozilla-inbound@a05b5362429f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.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. a=merge
browser/components/places/tests/browser/browser_library_left_pane_commands.js
browser/devtools/shared/test/browser_graphs-09.js
browser/devtools/timeline/widgets/overview.js
browser/themes/shared/devtools/highlighter.css
dom/base/nsGlobalWindow.cpp
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <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="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <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="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <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"/>
@@ -129,12 +129,12 @@
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="125ccf9bd5986c7728ea44508b3e1d1185ac028b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c5f8d282efe4a4e8b1e31a37300944e338e60e4f"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="91a2872824a81dea949f0b57a1399dade5d4d1ea"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="9663d24afcda9f96146185972ade780714727beb"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <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="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <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="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <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="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
@@ -148,13 +148,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="f8bec8a61dc0f2581fa72a31d4144084b47ef7cf"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
   <project name="platform/system/core" path="system/core" revision="350eac5403124dacb2a5fd9e28ac290a59fc3b8e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="91a2872824a81dea949f0b57a1399dade5d4d1ea"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="9663d24afcda9f96146185972ade780714727beb"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="d8952a42771045fca73ec600e2b42a4c7129d723"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="018b44e52b2bac5d3631d559550e88a4b68c6e67"/>
 </manifest>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <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"/>
@@ -140,13 +140,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="5e110615212302c5d798a3c223dcee458817651c"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="fa9ffd47948eb24466de227e48fe9c4a7c5e7711"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="cd76b19aafd4229ccf83853d02faef8c51ca8b34"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
   <project name="platform/system/core" path="system/core" revision="adc485d8755af6a61641d197de7cfef667722580"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="91a2872824a81dea949f0b57a1399dade5d4d1ea"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="9663d24afcda9f96146185972ade780714727beb"/>
   <project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4ae5df252123591d5b941191790e7abed1bce5a4"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="ce18b47b4a4f93a581d672bbd5cb6d12fe796ca9"/>
 </manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "c1bed74af46cb81a7092d6e80624134bae5d1bf0", 
+    "revision": "26b4d06af9d942c3b6ab81f119d33951823bc256", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- 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"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <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="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"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <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"/>
@@ -124,17 +124,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="91a2872824a81dea949f0b57a1399dade5d4d1ea"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="9663d24afcda9f96146185972ade780714727beb"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <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="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -250,16 +250,17 @@ pref("extensions.{972ce4c6-7e08-4474-a28
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties");
 
 pref("lightweightThemes.update.enabled", true);
 pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
 pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.footer.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#f2d9b1\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.footer.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-3\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/linen-light/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.footer.png\",\"textcolor\":\"#None\",\"accentcolor\":\"#ada8a8\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.preview.png\",\"author\":\"DVemer\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.footer.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"},{\"id\":\"recommended-5\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/carbon-light/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.footer.png\",\"textcolor\":\"#3b3b3b\",\"accentcolor\":\"#2e2e2e\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.preview.jpg\",\"author\":\"Jaxivo\",\"version\":\"1.0\"}]");
 
 // UI tour experience.
 pref("browser.uitour.enabled", true);
+pref("browser.uitour.loglevel", "Error");
 pref("browser.uitour.requireSecure", true);
 pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/");
 pref("browser.uitour.pinnedTabUrl", "https://support.mozilla.org/%LOCALE%/kb/pinned-tabs-keep-favorite-websites-open");
 pref("browser.uitour.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tour/");
 
 pref("browser.customizemode.tip0.shown", false);
 pref("browser.customizemode.tip0.learnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/customize");
 
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -69,16 +69,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
      * @param {string} [aReason] Some states are only shown if
      *                           a related reason is provided.
      *
      *                 aReason="login": Used after a login is completed
      *                   successfully. This is used so the state can be
      *                   temporarily shown until the next state change.
      */
     updateToolbarState: function(aReason = null) {
+      if (!this.toolbarButton.node) {
+        return;
+      }
       let state = "";
       if (MozLoopService.errors.size) {
         state = "error";
       } else if (aReason == "login" && MozLoopService.userProfile) {
         state = "active";
       } else if (MozLoopService.doNotDisturb) {
         state = "disabled";
       }
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -469,17 +469,18 @@ var PlacesCommandHook = {
    * Opens the Places Organizer. 
    * @param   aLeftPaneRoot
    *          The query to select in the organizer window - options
    *          are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
    *          UnfiledBookmarks, Tags and Downloads.
    */
   showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
     var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
-    if (!organizer) {
+    // Due to bug 528706, getMostRecentWindow can return closed windows.
+    if (!organizer || organizer.closed) {
       // No currently open places window, so open one with the specified mode.
       openDialog("chrome://browser/content/places/places.xul", 
                  "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
     }
     else {
       organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
       organizer.focus();
     }
--- a/browser/base/content/test/general/browser_restore_isAppTab.js
+++ b/browser/base/content/test/general/browser_restore_isAppTab.js
@@ -65,18 +65,22 @@ function loadFrameScript(browser) {
 }
 
 function isBrowserAppTab(browser) {
   return new Promise(resolve => {
     function listener({ data }) {
       browser.messageManager.removeMessageListener("Test:IsAppTab", listener);
       resolve(data.isAppTab);
     }
-    browser.messageManager.addMessageListener("Test:IsAppTab", listener);
-    browser.messageManager.sendAsyncMessage("Test:GetIsAppTab");
+    // It looks like same-process messages may be reordered by the message
+    // manager, so we need to wait one tick before sending the message.
+    executeSoon(function () {
+      browser.messageManager.addMessageListener("Test:IsAppTab", listener);
+      browser.messageManager.sendAsyncMessage("Test:GetIsAppTab");
+    });
   });
 }
 
 // Restarts the child process by crashing it then reloading the tab
 let restart = Task.async(function*(browser) {
   // If the tab isn't remote this would crash the main process so skip it
   if (!browser.isRemoteBrowser)
     return browser;
--- a/browser/base/content/test/general/browser_search_favicon.js
+++ b/browser/base/content/test/general/browser_search_favicon.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let gOriginalEngine;
 let gEngine;
 let gUnifiedCompletePref = "browser.urlbar.unifiedcomplete";
+let gRestyleSearchesPref = "browser.urlbar.restyleSearches";
 
 /**
  * Asynchronously adds visits to a page.
  *
  * @param aPlaceInfo
  *        Can be an nsIURI, in such a case a single LINK visit will be added.
  *        Otherwise can be an object describing the visit to add, or an array
  *        of these objects:
@@ -72,23 +73,25 @@ function* promiseAutocompleteResultPopup
   EventUtils.synthesizeKey(inputText.slice(-1) , {});
   yield promiseSearchComplete();
 
   return gURLBar.popup.richlistbox.children;
 }
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref(gUnifiedCompletePref);
+  Services.prefs.clearUserPref(gRestyleSearchesPref);
   Services.search.currentEngine = gOriginalEngine;
   Services.search.removeEngine(gEngine);
   return promiseClearHistory();
 });
 
 add_task(function*() {
   Services.prefs.setBoolPref(gUnifiedCompletePref, true);
+  Services.prefs.setBoolPref(gRestyleSearchesPref, true);
 });
 
 add_task(function*() {
 
   Services.search.addEngineWithDetails("SearchEngine", "", "", "",
                                        "GET", "http://s.example.com/search");
   gEngine = Services.search.getEngineByName("SearchEngine");
   gEngine.addParam("q", "{searchTerms}", null);
--- a/browser/base/content/test/general/browser_urlbarTrimURLs.js
+++ b/browser/base/content/test/general/browser_urlbarTrimURLs.js
@@ -36,31 +36,42 @@ function test() {
   testVal("http://ftp42.mozilla.org/", "http://ftp42.mozilla.org");
   testVal("http://ftpx.mozilla.org/", "ftpx.mozilla.org");
   testVal("ftp://ftp.mozilla.org/", "ftp://ftp.mozilla.org");
   testVal("ftp://ftp1.mozilla.org/", "ftp://ftp1.mozilla.org");
   testVal("ftp://ftp42.mozilla.org/", "ftp://ftp42.mozilla.org");
   testVal("ftp://ftpx.mozilla.org/", "ftp://ftpx.mozilla.org");
 
   testVal("https://user:pass@mozilla.org/", "https://user:pass@mozilla.org");
+  testVal("https://user@mozilla.org/", "https://user@mozilla.org");
   testVal("http://user:pass@mozilla.org/", "http://user:pass@mozilla.org");
+  testVal("http://user@mozilla.org/", "user@mozilla.org");
   testVal("http://sub.mozilla.org:666/", "sub.mozilla.org:666");
 
   testVal("https://[fe80::222:19ff:fe11:8c76]/file.ext");
   testVal("http://[fe80::222:19ff:fe11:8c76]/", "[fe80::222:19ff:fe11:8c76]");
   testVal("https://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
   testVal("http://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
 
   testVal("mailto:admin@mozilla.org");
   testVal("gopher://mozilla.org/");
   testVal("about:config");
   testVal("jar:http://mozilla.org/example.jar!/");
   testVal("view-source:http://mozilla.org/");
 
+  // Behaviour for hosts with no dots depends on the whitelist:
+  let fixupWhitelistPref = "browser.fixup.domainwhitelist.localhost";
+  Services.prefs.setBoolPref(fixupWhitelistPref, false);
   testVal("http://localhost");
+  Services.prefs.setBoolPref(fixupWhitelistPref, true);
+  testVal("http://localhost", "localhost");
+  Services.prefs.clearUserPref(fixupWhitelistPref);
+
+  testVal("http:// invalid url");
+
   testVal("http://someotherhostwithnodots");
   testVal("http://localhost/ foo bar baz");
   testVal("http://localhost.localdomain/ foo bar baz", "localhost.localdomain/ foo bar baz");
 
   Services.prefs.setBoolPref(prefname, false);
 
   testVal("http://mozilla.org/");
 
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -709,20 +709,30 @@ function openPrefsHelp() {
   openHelpLink(helpTopic, !instantApply);
 }
 
 function trimURL(aURL) {
   // This function must not modify the given URL such that calling
   // nsIURIFixup::createFixupURI with the result will produce a different URI.
 
   // remove single trailing slash for http/https/ftp URLs
-  let rv = aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
+  let url = aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
+
+  // remove http://
+  if (!url.startsWith("http://")) {
+    return url;
+  }
+  let urlWithoutProtocol = url.substring(7);
 
-  // Strip the leading http:// only if the host has at least one '.' or
-  // looks like an ipv6 ip:
-  let hostMatch = rv.match(/^http:\/\/([^\/]*)/);
-  let ipv6Regex = /\[[\da-f:]*\]/;
-  if (hostMatch && (hostMatch[1].contains(".") || ipv6Regex.test(hostMatch[1]))) {
-    /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
-    rv = rv.replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
+  let flags = Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
+              Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+  let fixedUpURL = Services.uriFixup.createFixupURI(urlWithoutProtocol, flags);
+  let expectedURLSpec;
+  try {
+    expectedURLSpec = makeURI(aURL).spec;
+  } catch (ex) {
+    return url;
   }
-  return rv;
+  if (fixedUpURL.spec == expectedURLSpec) {
+    return urlWithoutProtocol;
+  }
+  return url;
 }
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -126,17 +126,19 @@ let LoopRoomsInternal = {
    */
   getAll: function(version = null, callback) {
     if (!callback) {
       callback = version;
       version = null;
     }
 
     Task.spawn(function* () {
-      yield MozLoopService.promiseRegisteredWithServers();
+      let deferredInitialization = Promise.defer();
+      MozLoopService.delayedInitialize(deferredInitialization);
+      yield deferredInitialization.promise;
 
       if (!gDirty) {
         callback(null, [...this.rooms.values()]);
         return;
       }
 
       // Fetch the rooms from the server.
       let url = "/rooms" + (version ? "?version=" + encodeURIComponent(version) : "");
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -313,22 +313,27 @@ let MozLoopServiceInternal = {
   },
 
   get errors() {
     return gErrors;
   },
 
   /**
    * Get endpoints with the push server and register for notifications.
-   * For now we register as both a Guest and FxA user and all must succeed.
+   * This should only be called from promiseRegisteredWithServers to prevent reentrancy.
    *
+   * @param {LOOP_SESSION_TYPE} sessionType
    * @return {Promise} resolves with all push endpoints
    *                   rejects if any of the push registrations failed
    */
-  promiseRegisteredWithPushServer: function() {
+  promiseRegisteredWithPushServer: function(sessionType) {
+    if (!this.deferredRegistrations.has(sessionType)) {
+      return Promise.reject("promiseRegisteredWithPushServer must be called while there is a " +
+                            "deferred in deferredRegistrations in order to prevent reentrancy");
+    }
     // Wrap push notification registration call-back in a Promise.
     function registerForNotification(channelID, onNotification) {
       log.debug("registerForNotification", channelID);
       return new Promise((resolve, reject) => {
         function onRegistered(error, pushUrl) {
           log.debug("registerForNotification onRegistered:", error, pushUrl);
           if (error) {
             reject(Error(error));
@@ -347,29 +352,33 @@ let MozLoopServiceInternal = {
 
         MozLoopServiceInternal.pushHandler.register(channelID, onRegistered, onNotification);
       });
     }
 
     let options = this.mocks.webSocket ? { mockWebSocket: this.mocks.webSocket } : {};
     this.pushHandler.initialize(options);
 
-    let callsRegGuest = registerForNotification(MozLoopService.channelIDs.callsGuest,
+    if (sessionType == LOOP_SESSION_TYPE.GUEST) {
+      let callsRegGuest = registerForNotification(MozLoopService.channelIDs.callsGuest,
+                                                  LoopCalls.onNotification);
+
+      let roomsRegGuest = registerForNotification(MozLoopService.channelIDs.roomsGuest,
+                                                  roomsPushNotification);
+      return Promise.all([callsRegGuest, roomsRegGuest]);
+    } else if (sessionType == LOOP_SESSION_TYPE.FXA) {
+      let callsRegFxA = registerForNotification(MozLoopService.channelIDs.callsFxA,
                                                 LoopCalls.onNotification);
 
-    let roomsRegGuest = registerForNotification(MozLoopService.channelIDs.roomsGuest,
+      let roomsRegFxA = registerForNotification(MozLoopService.channelIDs.roomsFxA,
                                                 roomsPushNotification);
+      return Promise.all([callsRegFxA, roomsRegFxA]);
+    }
 
-    let callsRegFxA = registerForNotification(MozLoopService.channelIDs.callsFxA,
-                                              LoopCalls.onNotification);
-
-    let roomsRegFxA = registerForNotification(MozLoopService.channelIDs.roomsFxA,
-                                              roomsPushNotification);
-
-    return Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA]);
+    return Promise.reject("promiseRegisteredWithPushServer: Invalid sessionType");
   },
 
   /**
    * Starts registration of Loop with the push server, and then will register
    * with the Loop server. It will return early if already registered.
    *
    * @param {LOOP_SESSION_TYPE} sessionType
    * @returns {Promise} a promise that is resolved with no params on completion, or
@@ -384,17 +393,17 @@ let MozLoopServiceInternal = {
     let result = null;
     let deferred = Promise.defer();
     log.debug("assigning to deferredRegistrations for sessionType:", sessionType);
     this.deferredRegistrations.set(sessionType, deferred);
 
     // We grab the promise early in case one of the callers below delete it from the map.
     result = deferred.promise;
 
-    this.promiseRegisteredWithPushServer().then(() => {
+    this.promiseRegisteredWithPushServer(sessionType).then(() => {
       return this.registerWithLoopServer(sessionType);
     }).then(() => {
       deferred.resolve("registered to status:" + sessionType);
       // No need to clear the promise here, everything was good, so we don't need
       // to re-register.
     }, error => {
       log.error("Failed to register with Loop server with sessionType " + sessionType, error);
       deferred.reject(error);
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -227,17 +227,20 @@ loop.roomViews = (function(mozL10n) {
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
         "local-stream-audio": !this.state.videoMuted,
         "room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
       });
 
       switch(this.state.roomState) {
-        case ROOM_STATES.FAILED: {
+        case ROOM_STATES.FAILED:
+        case ROOM_STATES.FULL: {
+          // Note: While rooms are set to hold a maximum of 2 participants, the
+          //       FULL case should never happen on desktop.
           return loop.conversation.GenericFailureView({
             cancelCall: this.closeWindow}
           );
         }
         default: {
           return (
             React.DOM.div({className: "room-conversation-wrapper"}, 
               this._renderInvitationOverlay(), 
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -227,17 +227,20 @@ loop.roomViews = (function(mozL10n) {
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
         "local-stream-audio": !this.state.videoMuted,
         "room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
       });
 
       switch(this.state.roomState) {
-        case ROOM_STATES.FAILED: {
+        case ROOM_STATES.FAILED:
+        case ROOM_STATES.FULL: {
+          // Note: While rooms are set to hold a maximum of 2 participants, the
+          //       FULL case should never happen on desktop.
           return <loop.conversation.GenericFailureView
             cancelCall={this.closeWindow}
           />;
         }
         default: {
           return (
             <div className="room-conversation-wrapper">
               {this._renderInvitationOverlay()}
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -748,25 +748,32 @@ html, .fx-embedded, #main,
   top: 35%;
   left: 0;
   right: 25%;
   z-index: 1000;
   margin: 0 auto;
   width: 50%;
   color: #fff;
   font-weight: bold;
+  font-size: 1.1em;
 }
 
 .standalone .room-inner-info-area button {
   border-radius: 3px;
   font-size: 1.2em;
   padding: .2em 1.2em;
   cursor: pointer;
 }
 
+.standalone .room-inner-info-area a.btn {
+  padding: .5em 3em .3em 3em;
+  border-radius: 3px;
+  font-weight: normal;
+}
+
 .standalone .room-conversation h2.room-name {
   position: absolute;
   display: inline-block;
   top: 0;
   right: 0;
   color: #fff;
   z-index: 2000000;
   font-size: 1.2em;
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -20,17 +20,19 @@ loop.store.ActiveRoomStore = (function()
     READY: "room-ready",
     // The room is known to be joined on the loop-server
     JOINED: "room-joined",
     // The room is connected to the sdk server.
     SESSION_CONNECTED: "room-session-connected",
     // There are participants in the room.
     HAS_PARTICIPANTS: "room-has-participants",
     // There was an issue with the room
-    FAILED: "room-failed"
+    FAILED: "room-failed",
+    // The room is full
+    FULL: "room-full"
   };
 
   /**
    * Store for things that are local to this instance (in this profile, on
    * this machine) of this roomRoom store, in addition to a mirror of some
    * remote-state.
    *
    * @extends {Backbone.Events}
@@ -100,28 +102,28 @@ loop.store.ActiveRoomStore = (function()
     setStoreState: function(newState) {
       for (var key in newState) {
         this._storeState[key] = newState[key];
       }
       this.trigger("change");
     },
 
     /**
-     * Handles a room failure. Currently this prints the error to the console
-     * and sets the roomState to failed.
+     * Handles a room failure.
      *
      * @param {sharedActions.RoomFailure} actionData
      */
     roomFailure: function(actionData) {
       console.error("Error in state `" + this._storeState.roomState + "`:",
         actionData.error);
 
       this.setStoreState({
         error: actionData.error,
-        roomState: ROOM_STATES.FAILED
+        roomState: actionData.error.errno === 202 ? ROOM_STATES.FULL
+                                                  : ROOM_STATES.FAILED
       });
     },
 
     /**
      * Registers the actions with the dispatcher that this store is interested
      * in.
      */
     _registerActions: function() {
@@ -357,16 +359,20 @@ loop.store.ActiveRoomStore = (function()
     /**
      * Handles leaving a room. Clears any membership timeouts, then
      * signals to the server the leave of the room.
      *
      * @param {ROOM_STATES} nextState Optional; the next state to switch to.
      *                                Switches to READY if undefined.
      */
     _leaveRoom: function(nextState) {
+      if (loop.standaloneMedia) {
+        loop.standaloneMedia.multiplexGum.reset();
+      }
+
       this._sdkDriver.disconnectSession();
 
       if (this._timeout) {
         clearTimeout(this._timeout);
         delete this._timeout;
       }
 
       if (this._storeState.roomState === ROOM_STATES.JOINED ||
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -172,18 +172,16 @@ loop.shared.models = (function(l10n) {
       });
 
       this.session = this.sdk.initSession(this.get("sessionId"));
       this.listenTo(this.session, "streamCreated", this._streamCreated);
       this.listenTo(this.session, "connectionDestroyed",
                                   this._connectionDestroyed);
       this.listenTo(this.session, "sessionDisconnected",
                                   this._sessionDisconnected);
-      this.listenTo(this.session, "networkDisconnected",
-                                  this._networkDisconnected);
       this.session.connect(this.get("apiKey"), this.get("sessionToken"),
                            this._onConnectCompletion.bind(this));
     },
 
     /**
      * Ends current session.
      */
     endSession: function() {
@@ -318,46 +316,41 @@ loop.shared.models = (function(l10n) {
 
     /**
      * Local user hung up.
      * http://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
      *
      * @param  {SessionDisconnectEvent} event
      */
     _sessionDisconnected: function(event) {
+      if(event.reason === "networkDisconnected") {
+        this._signalEnd("session:network-disconnected", event);
+      } else {
+        this._signalEnd("session:ended", event);
+      }
+    },
+
+    _signalEnd: function(eventName, event) {
       this.set("connected", false)
           .set("ongoing", false)
-          .trigger("session:ended");
+          .trigger(eventName, event);
     },
 
     /**
      * Peer hung up. Disconnects local session.
      * http://tokbox.com/opentok/libraries/client/js/reference/ConnectionEvent.html
      *
      * @param  {ConnectionEvent} event
      */
     _connectionDestroyed: function(event) {
-      this.set("connected", false)
-          .set("ongoing", false)
-          .trigger("session:peer-hungup", {
-            connectionId: event.connection.connectionId
-          });
-      this.endSession();
-    },
-
-    /**
-     * Network was disconnected.
-     * http://tokbox.com/opentok/libraries/client/js/reference/ConnectionEvent.html
-     *
-     * @param {ConnectionEvent} event
-     */
-    _networkDisconnected: function(event) {
-      this.set("connected", false)
-          .set("ongoing", false)
-          .trigger("session:network-disconnected");
+      if (event.reason === "networkDisconnected") {
+        this._signalEnd("session:network-disconnected", event);
+      } else {
+        this._signalEnd("session:peer-hungup", event);
+      }
       this.endSession();
     },
   });
 
   /**
    * Notification model.
    */
   var NotificationModel = Backbone.Model.extend({
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -13,16 +13,17 @@
 # to the Gruntfile and getting rid of this Makefile entirely.
 
 LOOP_SERVER_URL := $(shell echo $${LOOP_SERVER_URL-http://localhost:5000})
 LOOP_FEEDBACK_API_URL := $(shell echo $${LOOP_FEEDBACK_API_URL-"https://input.allizom.org/api/v1/feedback"})
 LOOP_FEEDBACK_PRODUCT_NAME := $(shell echo $${LOOP_FEEDBACK_PRODUCT_NAME-Loop})
 LOOP_BRAND_WEBSITE_URL := $(shell echo $${LOOP_BRAND_WEBSITE_URL-"https://www.mozilla.org/firefox/"})
 LOOP_PRIVACY_WEBSITE_URL := $(shell echo $${LOOP_PRIVACY_WEBSITE_URL-"https://www.mozilla.org/privacy"})
 LOOP_LEGAL_WEBSITE_URL := $(shell echo $${LOOP_LEGAL_WEBSITE_URL-"/legal/terms"})
+LOOP_PRODUCT_HOMEPAGE_URL := $(shell echo $${LOOP_PRODUCT_HOMEPAGE_URL-"https://www.firefox.com/hello/"})
 
 NODE_LOCAL_BIN=./node_modules/.bin
 
 install: npm_install tos
 
 npm_install:
 	@npm install
 
@@ -74,11 +75,12 @@ 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.feedbackApiUrl = '`echo $(LOOP_FEEDBACK_API_URL)`';" >> content/config.js
 	@echo "loop.config.feedbackProductName = '`echo $(LOOP_FEEDBACK_PRODUCT_NAME)`';" >> content/config.js
 	@echo "loop.config.brandWebsiteUrl = '`echo $(LOOP_BRAND_WEBSITE_URL)`';" >> content/config.js
 	@echo "loop.config.privacyWebsiteUrl = '`echo $(LOOP_PRIVACY_WEBSITE_URL)`';" >> content/config.js
 	@echo "loop.config.legalWebsiteUrl = '`echo $(LOOP_LEGAL_WEBSITE_URL)`';" >> content/config.js
+	@echo "loop.config.learnMoreUrl = '`echo $(LOOP_PRODUCT_HOMEPAGE_URL)`';" >> content/config.js
 	@echo "loop.config.fxosApp = loop.config.fxosApp || {};" >> content/config.js
 	@echo "loop.config.fxosApp.name = 'Loop';" >> content/config.js
 	@echo "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';" >> content/config.js
--- a/browser/components/loop/standalone/content/js/standaloneMozLoop.js
+++ b/browser/components/loop/standalone/content/js/standaloneMozLoop.js
@@ -42,17 +42,17 @@ loop.StandaloneMozLoop = (function(mozL1
    * @param textStatus See jQuery docs
    * @param errorThrown See jQuery docs
    */
   function failureHandler(callback, jqXHR, textStatus, errorThrown) {
     var jsonErr = jqXHR && jqXHR.responseJSON || {};
     var message = "HTTP " + jqXHR.status + " " + errorThrown;
 
     // Create an error with server error `errno` code attached as a property
-    var err = new Error(message);
+    var err = new Error(message + (jsonErr.error ? "; " + jsonErr.error : ""));
     err.errno = jsonErr.errno;
 
     callback(err);
   }
 
   /**
    * StandaloneMozLoopRooms is used as part of StandaloneMozLoop to define
    * the rooms sub-object. We do it this way so that we can share the options
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -1,31 +1,99 @@
 /** @jsx React.DOM */
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global loop:true, React */
+/* jshint newcap:false, maxlen:false */
 
 var loop = loop || {};
 loop.standaloneRoomViews = (function(mozL10n) {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
   var sharedViews = loop.shared.views;
 
+  var StandaloneRoomInfoArea = React.createClass({displayName: 'StandaloneRoomInfoArea',
+    propTypes: {
+      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
+    },
+
+    _renderCallToActionLink: function() {
+      if (this.props.helper.isFirefox(navigator.userAgent)) {
+        return (
+          React.DOM.a({href: loop.config.learnMoreUrl, className: "btn btn-info"}, 
+            mozL10n.get("rooms_room_full_call_to_action_label", {
+              clientShortname: mozL10n.get("clientShortname2")
+            })
+          )
+        );
+      }
+      return (
+        React.DOM.a({href: loop.config.brandWebsiteUrl, className: "btn btn-info"}, 
+          mozL10n.get("rooms_room_full_call_to_action_nonFx_label", {
+            brandShortname: mozL10n.get("brandShortname")
+          })
+        )
+      );
+    },
+
+    _renderContent: function() {
+      switch(this.props.roomState) {
+        case ROOM_STATES.INIT:
+        case ROOM_STATES.READY: {
+          return (
+            React.DOM.button({className: "btn btn-join btn-info", 
+                    onClick: this.props.joinRoom}, 
+              mozL10n.get("rooms_room_join_label")
+            )
+          );
+        }
+        case ROOM_STATES.JOINED:
+        case ROOM_STATES.SESSION_CONNECTED: {
+          return (
+            React.DOM.p({className: "empty-room-message"}, 
+              mozL10n.get("rooms_only_occupant_label")
+            )
+          );
+        }
+        case ROOM_STATES.FULL:
+          return (
+            React.DOM.div(null, 
+              React.DOM.p({className: "full-room-message"}, 
+                mozL10n.get("rooms_room_full_label")
+              ), 
+              React.DOM.p(null, this._renderCallToActionLink())
+            )
+          );
+        default:
+          return null;
+      }
+    },
+
+    render: function() {
+      return (
+        React.DOM.div({className: "room-inner-info-area"}, 
+          this._renderContent()
+        )
+      );
+    }
+  });
+
   var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
     mixins: [Backbone.Events],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
@@ -124,56 +192,29 @@ loop.standaloneRoomViews = (function(moz
      * @return {Boolean}
      */
     _roomIsActive: function() {
       return this.state.roomState === ROOM_STATES.JOINED            ||
              this.state.roomState === ROOM_STATES.SESSION_CONNECTED ||
              this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
     },
 
-    _renderContextualRoomInfo: function() {
-      switch(this.state.roomState) {
-        case ROOM_STATES.INIT:
-        case ROOM_STATES.READY: {
-          // Join button
-          return (
-            React.DOM.div({className: "room-inner-info-area"}, 
-              React.DOM.button({className: "btn btn-join btn-info", onClick: this.joinRoom}, 
-                mozL10n.get("rooms_room_join_label")
-              )
-            )
-          );
-        }
-        case ROOM_STATES.JOINED:
-        case ROOM_STATES.SESSION_CONNECTED: {
-          // Empty room message
-          return (
-            React.DOM.div({className: "room-inner-info-area"}, 
-              React.DOM.p({className: "empty-room-message"}, 
-                mozL10n.get("rooms_only_occupant_label")
-              )
-            )
-          );
-        }
-      }
-      // XXX Render "Start your own" button when room is over capacity (see
-      //     bug 1074709)
-    },
-
     render: function() {
       var localStreamClasses = React.addons.classSet({
         hide: !this._roomIsActive(),
         local: true,
         "local-stream": true,
         "local-stream-audio": false
       });
 
       return (
         React.DOM.div({className: "room-conversation-wrapper"}, 
-          this._renderContextualRoomInfo(), 
+          StandaloneRoomInfoArea({roomState: this.state.roomState, 
+                                  joinRoom: this.joinRoom, 
+                                  helper: this.props.helper}), 
           React.DOM.div({className: "video-layout-wrapper"}, 
             React.DOM.div({className: "conversation room-conversation"}, 
               React.DOM.h2({className: "room-name"}, this.state.roomName), 
               React.DOM.div({className: "media nested"}, 
                 React.DOM.div({className: "video_wrapper remote_wrapper"}, 
                   React.DOM.div({className: "video_inner remote"})
                 ), 
                 React.DOM.div({className: localStreamClasses})
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -1,31 +1,99 @@
 /** @jsx React.DOM */
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global loop:true, React */
+/* jshint newcap:false, maxlen:false */
 
 var loop = loop || {};
 loop.standaloneRoomViews = (function(mozL10n) {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
   var sharedViews = loop.shared.views;
 
+  var StandaloneRoomInfoArea = React.createClass({
+    propTypes: {
+      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
+    },
+
+    _renderCallToActionLink: function() {
+      if (this.props.helper.isFirefox(navigator.userAgent)) {
+        return (
+          <a href={loop.config.learnMoreUrl} className="btn btn-info">
+            {mozL10n.get("rooms_room_full_call_to_action_label", {
+              clientShortname: mozL10n.get("clientShortname2")
+            })}
+          </a>
+        );
+      }
+      return (
+        <a href={loop.config.brandWebsiteUrl} className="btn btn-info">
+          {mozL10n.get("rooms_room_full_call_to_action_nonFx_label", {
+            brandShortname: mozL10n.get("brandShortname")
+          })}
+        </a>
+      );
+    },
+
+    _renderContent: function() {
+      switch(this.props.roomState) {
+        case ROOM_STATES.INIT:
+        case ROOM_STATES.READY: {
+          return (
+            <button className="btn btn-join btn-info"
+                    onClick={this.props.joinRoom}>
+              {mozL10n.get("rooms_room_join_label")}
+            </button>
+          );
+        }
+        case ROOM_STATES.JOINED:
+        case ROOM_STATES.SESSION_CONNECTED: {
+          return (
+            <p className="empty-room-message">
+              {mozL10n.get("rooms_only_occupant_label")}
+            </p>
+          );
+        }
+        case ROOM_STATES.FULL:
+          return (
+            <div>
+              <p className="full-room-message">
+                {mozL10n.get("rooms_room_full_label")}
+              </p>
+              <p>{this._renderCallToActionLink()}</p>
+            </div>
+          );
+        default:
+          return null;
+      }
+    },
+
+    render: function() {
+      return (
+        <div className="room-inner-info-area">
+          {this._renderContent()}
+        </div>
+      );
+    }
+  });
+
   var StandaloneRoomView = React.createClass({
     mixins: [Backbone.Events],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
     getInitialState: function() {
       var storeState = this.props.activeRoomStore.getStoreState();
       return _.extend({}, storeState, {
         // Used by the UI showcase.
         roomState: this.props.roomState || storeState.roomState
       });
@@ -124,56 +192,29 @@ loop.standaloneRoomViews = (function(moz
      * @return {Boolean}
      */
     _roomIsActive: function() {
       return this.state.roomState === ROOM_STATES.JOINED            ||
              this.state.roomState === ROOM_STATES.SESSION_CONNECTED ||
              this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
     },
 
-    _renderContextualRoomInfo: function() {
-      switch(this.state.roomState) {
-        case ROOM_STATES.INIT:
-        case ROOM_STATES.READY: {
-          // Join button
-          return (
-            <div className="room-inner-info-area">
-              <button className="btn btn-join btn-info" onClick={this.joinRoom}>
-                {mozL10n.get("rooms_room_join_label")}
-              </button>
-            </div>
-          );
-        }
-        case ROOM_STATES.JOINED:
-        case ROOM_STATES.SESSION_CONNECTED: {
-          // Empty room message
-          return (
-            <div className="room-inner-info-area">
-              <p className="empty-room-message">
-                {mozL10n.get("rooms_only_occupant_label")}
-              </p>
-            </div>
-          );
-        }
-      }
-      // XXX Render "Start your own" button when room is over capacity (see
-      //     bug 1074709)
-    },
-
     render: function() {
       var localStreamClasses = React.addons.classSet({
         hide: !this._roomIsActive(),
         local: true,
         "local-stream": true,
         "local-stream-audio": false
       });
 
       return (
         <div className="room-conversation-wrapper">
-          {this._renderContextualRoomInfo()}
+          <StandaloneRoomInfoArea roomState={this.state.roomState}
+                                  joinRoom={this.joinRoom}
+                                  helper={this.props.helper} />
           <div className="video-layout-wrapper">
             <div className="conversation room-conversation">
               <h2 className="room-name">{this.state.roomName}</h2>
               <div className="media nested">
                 <div className="video_wrapper remote_wrapper">
                   <div className="video_inner remote"></div>
                 </div>
                 <div className={localStreamClasses}></div>
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -934,17 +934,18 @@ loop.webapp = (function($, _, OT, mozL10
                feedbackApiClient: this.props.feedbackApiClient}
             )
           );
         }
         case "room": {
           return (
             loop.standaloneRoomViews.StandaloneRoomView({
               activeRoomStore: this.props.activeRoomStore, 
-              dispatcher: this.props.dispatcher}
+              dispatcher: this.props.dispatcher, 
+              helper: this.props.helper}
             )
           );
         }
         case "home": {
           return HomeView(null);
         }
         default: {
           // The state hasn't been initialised yet, so don't display
--- a/browser/components/loop/standalone/content/js/webapp.jsx
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -935,16 +935,17 @@ loop.webapp = (function($, _, OT, mozL10
             />
           );
         }
         case "room": {
           return (
             <loop.standaloneRoomViews.StandaloneRoomView
               activeRoomStore={this.props.activeRoomStore}
               dispatcher={this.props.dispatcher}
+              helper={this.props.helper}
             />
           );
         }
         case "home": {
           return <HomeView />;
         }
         default: {
           // The state hasn't been initialised yet, so don't display
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -21,16 +21,17 @@ function getConfigFile(req, res) {
     "loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';",
     "loop.config.feedbackApiUrl = '" + feedbackApiUrl + "';",
     "loop.config.feedbackProductName = '" + feedbackProductName + "';",
     // XXX Update with the real marketplace url once the FxOS Loop app is
     //     uploaded to the marketplace bug 1053424
     "loop.config.marketplaceUrl = 'http://fake-market.herokuapp.com/iframe-install.html'",
     "loop.config.brandWebsiteUrl = 'https://www.mozilla.org/firefox/';",
     "loop.config.privacyWebsiteUrl = 'https://www.mozilla.org/privacy';",
+    "loop.config.learnMoreUrl = 'https://www.mozilla.org/hello/';",
     "loop.config.legalWebsiteUrl = '/legal/terms';",
     "loop.config.fxosApp = loop.config.fxosApp || {};",
     "loop.config.fxosApp.name = 'Loop';",
     "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';"
   ].join("\n"));
 }
 
 app.get('/content/config.js', getConfigFile);
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -241,16 +241,26 @@ describe("loop.roomViews", function () {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.FAILED});
 
           view = mountTestComponent();
 
           TestUtils.findRenderedComponentWithType(view,
             loop.conversation.GenericFailureView);
         });
 
+      it("should render the GenericFailureView if the roomState is `FULL`",
+        function() {
+          activeRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
+
+          view = mountTestComponent();
+
+          TestUtils.findRenderedComponentWithType(view,
+            loop.conversation.GenericFailureView);
+        });
+
       it("should render the DesktopRoomInvitationView if roomState is `JOINED`",
         function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
 
           view = mountTestComponent();
 
           TestUtils.findRenderedComponentWithType(view,
             loop.roomViews.DesktopRoomInvitationView);
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -1,18 +1,19 @@
-/* global chai */
+/* global chai, loop */
 
 var expect = chai.expect;
 var sharedActions = loop.shared.actions;
 
 describe("loop.store.ActiveRoomStore", function () {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
+  var fakeMultiplexGum;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers();
 
     dispatcher = new loop.Dispatcher();
     sandbox.stub(dispatcher, "dispatch");
 
@@ -25,16 +26,24 @@ describe("loop.store.ActiveRoomStore", f
       }
     };
 
     fakeSdkDriver = {
       connectSession: sandbox.stub(),
       disconnectSession: sandbox.stub()
     };
 
+    fakeMultiplexGum = {
+        reset: sandbox.spy()
+    };
+
+    loop.standaloneMedia = {
+      multiplexGum: fakeMultiplexGum
+    };
+
     store = new loop.store.ActiveRoomStore({
       dispatcher: dispatcher,
       mozLoop: fakeMozLoop,
       sdkDriver: fakeSdkDriver
     });
   });
 
   afterEach(function() {
@@ -77,17 +86,25 @@ describe("loop.store.ActiveRoomStore", f
     it("should log the error", function() {
       store.roomFailure({error: fakeError});
 
       sinon.assert.calledOnce(console.error);
       sinon.assert.calledWith(console.error,
         sinon.match(ROOM_STATES.READY), fakeError);
     });
 
-    it("should set the state to `FAILED`", function() {
+    it("should set the state to `FULL` on server errno 202", function() {
+      fakeError.errno = 202;
+
+      store.roomFailure({error: fakeError});
+
+      expect(store._storeState.roomState).eql(ROOM_STATES.FULL);
+    });
+
+    it("should set the state to `FAILED` on generic error", function() {
       store.roomFailure({error: fakeError});
 
       expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
     });
   });
 
   describe("#setupWindowData", function() {
     var fakeToken, fakeRoomData;
@@ -366,16 +383,22 @@ describe("loop.store.ActiveRoomStore", f
     beforeEach(function() {
       store.setStoreState({
         roomState: ROOM_STATES.JOINED,
         roomToken: "fakeToken",
         sessionToken: "1627384950"
       });
     });
 
+    it("should reset the multiplexGum", function() {
+      store.leaveRoom();
+
+      sinon.assert.calledOnce(fakeMultiplexGum.reset);
+    });
+
     it("should disconnect from the servers via the sdk", function() {
       store.connectionFailure();
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
     });
 
     it("should clear any existing timeout", function() {
       sandbox.stub(window, "clearTimeout");
@@ -445,16 +468,22 @@ describe("loop.store.ActiveRoomStore", f
     beforeEach(function() {
       store.setStoreState({
         roomState: ROOM_STATES.JOINED,
         roomToken: "fakeToken",
         sessionToken: "1627384950"
       });
     });
 
+    it("should reset the multiplexGum", function() {
+      store.leaveRoom();
+
+      sinon.assert.calledOnce(fakeMultiplexGum.reset);
+    });
+
     it("should disconnect from the servers via the sdk", function() {
       store.windowUnload();
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
     });
 
     it("should clear any existing timeout", function() {
       sandbox.stub(window, "clearTimeout");
@@ -484,16 +513,22 @@ describe("loop.store.ActiveRoomStore", f
     beforeEach(function() {
       store.setStoreState({
         roomState: ROOM_STATES.JOINED,
         roomToken: "fakeToken",
         sessionToken: "1627384950"
       });
     });
 
+    it("should reset the multiplexGum", function() {
+      store.leaveRoom();
+
+      sinon.assert.calledOnce(fakeMultiplexGum.reset);
+    });
+
     it("should disconnect from the servers via the sdk", function() {
       store.leaveRoom();
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
     });
 
     it("should clear any existing timeout", function() {
       sandbox.stub(window, "clearTimeout");
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -248,16 +248,30 @@ describe("loop.shared.models", function(
 
           it("should trigger a session:ended event on sessionDisconnected",
             function(done) {
               model.once("session:ended", function(){ done(); });
 
               fakeSession.trigger("sessionDisconnected", {reason: "ko"});
             });
 
+          it("should trigger network-disconnected on networkDisconnect reason",
+             function(done) {
+               model.once("session:network-disconnected", function() {
+                 done();
+               });
+
+               var fakeEvent = {
+                 connectionId: 42,
+                 reason: "networkDisconnected"
+               };
+
+               fakeSession.trigger("sessionDisconnected", fakeEvent);
+            });
+
           it("should set the connected attribute to false on sessionDisconnected",
             function() {
               fakeSession.trigger("sessionDisconnected", {reason: "ko"});
 
               expect(model.get("connected")).eql(false);
             });
 
           it("should set the ongoing attribute to false on sessionDisconnected",
@@ -268,50 +282,31 @@ describe("loop.shared.models", function(
             });
 
           describe("connectionDestroyed event received", function() {
             var fakeEvent = {reason: "ko", connection: {connectionId: 42}};
 
             it("should trigger a session:peer-hungup model event",
               function(done) {
                 model.once("session:peer-hungup", function(event) {
-                  expect(event.connectionId).eql(42);
+                  expect(event.connection.connectionId).eql(42);
                   done();
                 });
 
                 fakeSession.trigger("connectionDestroyed", fakeEvent);
               });
 
             it("should terminate the session", function() {
               sandbox.stub(model, "endSession");
 
               fakeSession.trigger("connectionDestroyed", fakeEvent);
 
               sinon.assert.calledOnce(model.endSession);
             });
           });
-
-          describe("networkDisconnected event received", function() {
-            it("should trigger a session:network-disconnected event",
-              function(done) {
-                model.once("session:network-disconnected", function() {
-                  done();
-                });
-
-                fakeSession.trigger("networkDisconnected");
-              });
-
-            it("should terminate the session", function() {
-              sandbox.stub(model, "endSession");
-
-              fakeSession.trigger("networkDisconnected", {reason: "ko"});
-
-              sinon.assert.calledOnce(model.endSession);
-            });
-          });
         });
       });
 
       describe("#endSession", function() {
         var model;
 
         beforeEach(function() {
           model = new sharedModels.ConversationModel(fakeSessionData, {
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -29,17 +29,18 @@ describe("loop.standaloneRoomViews", fun
     sandbox.restore();
   });
 
   describe("standaloneRoomView", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         loop.standaloneRoomViews.StandaloneRoomView({
           dispatcher: dispatcher,
-          activeRoomStore: activeRoomStore
+          activeRoomStore: activeRoomStore,
+          helper: new loop.shared.utils.Helper()
         }));
     }
 
     describe("#componentWillUpdate", function() {
       it("dispatch an `SetupStreamElements` action on room joined", function() {
         activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
         var view = mountTestComponent();
 
@@ -123,16 +124,26 @@ describe("loop.standaloneRoomViews", fun
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .eql(null);
           });
       });
 
+      describe("Full room message", function() {
+        it("should display a full room message on FULL",
+          function() {
+            activeRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
+
+            expect(view.getDOMNode().querySelector(".full-room-message"))
+              .not.eql(null);
+          });
+      });
+
       describe("Join button", function() {
         function getJoinButton(view) {
           return view.getDOMNode().querySelector(".btn-join");
         }
 
         it("should render the Join button when room isn't active", function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
 
@@ -170,16 +181,23 @@ describe("loop.standaloneRoomViews", fun
 
         it("should disable the Leave button when the room state is FAILED",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.FAILED});
 
             expect(getLeaveButton(view).disabled).eql(true);
           });
 
+        it("should disable the Leave button when the room state is FULL",
+          function() {
+            activeRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
+
+            expect(getLeaveButton(view).disabled).eql(true);
+          });
+
         it("should enable the Leave button when the room state is SESSION_CONNECTED",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
 
             expect(getLeaveButton(view).disabled).eql(false);
           });
 
         it("should enable the Leave button when the room state is JOINED",
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -2,131 +2,126 @@
  * 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 { LoopCallsInternal } = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 
+let actionReceived = false;
 let openChatOrig = Chat.open;
 
 const firstCallId = 4444333221;
 const secondCallId = 1001100101;
 
 let msgHandler = function(msg) {
   if (msg.messageType &&
       msg.messageType === "action" &&
       msg.event === "terminate" &&
       msg.reason === "busy") {
     actionReceived = true;
   }
 };
 
-add_test(function test_busy_2guest_calls() {
+add_task(function* test_busy_2guest_calls() {
   actionReceived = false;
 
   mockPushHandler.registrationPushURL = kEndPointUrl;
 
-  MozLoopService.promiseRegisteredWithServers().then(() => {
-    let opened = 0;
-    let windowId;
-    Chat.open = function(contentWindow, origin, title, url) {
-      opened++;
-      windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
-    };
+  yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.GUEST);
 
-    mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
+  let opened = 0;
+  let windowId;
+  Chat.open = function(contentWindow, origin, title, url) {
+    opened++;
+    windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
+  };
 
-    waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
-      do_check_true(opened === 1, "should open only one chat window");
-      do_check_true(actionReceived, "should respond with busy/reject to second call");
-      LoopCalls.clearCallInProgress(windowId);
-      run_next_test();
-    }, () => {
-      do_throw("should have opened a chat window for first call and rejected second call");
-    });
+  mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
+  yield waitForCondition(() => { return actionReceived && opened > 0; }).then(() => {
+    do_check_true(opened === 1, "should open only one chat window");
+    do_check_true(actionReceived, "should respond with busy/reject to second call");
+    LoopCalls.clearCallInProgress(windowId);
+  }, () => {
+    do_throw("should have opened a chat window for first call and rejected second call");
   });
 });
 
-add_test(function test_busy_1fxa_1guest_calls() {
+add_task(function* test_busy_1fxa_1guest_calls() {
   actionReceived = false;
 
-  MozLoopService.promiseRegisteredWithServers().then(() => {
-    let opened = 0;
-    let windowId;
-    Chat.open = function(contentWindow, origin, title, url) {
-      opened++;
-      windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
-    };
+  yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.GUEST);
+  yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA);
+
+  let opened = 0;
+  let windowId;
+  Chat.open = function(contentWindow, origin, title, url) {
+    opened++;
+    windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
+  };
 
-    mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
-    mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
+  mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
+  mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
-    waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
-      do_check_true(opened === 1, "should open only one chat window");
-      do_check_true(actionReceived, "should respond with busy/reject to second call");
-      LoopCalls.clearCallInProgress(windowId);
-      run_next_test();
-    }, () => {
-      do_throw("should have opened a chat window for first call and rejected second call");
-    });
-
+  yield waitForCondition(() => { return actionReceived && opened > 0; }).then(() => {
+    do_check_true(opened === 1, "should open only one chat window");
+    do_check_true(actionReceived, "should respond with busy/reject to second call");
+    LoopCalls.clearCallInProgress(windowId);
+  }, () => {
+    do_throw("should have opened a chat window for first call and rejected second call");
   });
 });
 
-add_test(function test_busy_2fxa_calls() {
+add_task(function* test_busy_2fxa_calls() {
   actionReceived = false;
 
-  MozLoopService.promiseRegisteredWithServers().then(() => {
-    let opened = 0;
-    let windowId;
-    Chat.open = function(contentWindow, origin, title, url) {
-      opened++;
-      windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
-    };
+  yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA);
 
-    mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
+  let opened = 0;
+  let windowId;
+  Chat.open = function(contentWindow, origin, title, url) {
+    opened++;
+    windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
+  };
 
-    waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
-      do_check_true(opened === 1, "should open only one chat window");
-      do_check_true(actionReceived, "should respond with busy/reject to second call");
-      LoopCalls.clearCallInProgress(windowId);
-      run_next_test();
-    }, () => {
-      do_throw("should have opened a chat window for first call and rejected second call");
-    });
+  mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
 
+  yield waitForCondition(() => { return actionReceived && opened > 0; }).then(() => {
+    do_check_true(opened === 1, "should open only one chat window");
+    do_check_true(actionReceived, "should respond with busy/reject to second call");
+    LoopCalls.clearCallInProgress(windowId);
+  }, () => {
+    do_throw("should have opened a chat window for first call and rejected second call");
   });
 });
 
-add_test(function test_busy_1guest_1fxa_calls() {
+add_task(function* test_busy_1guest_1fxa_calls() {
   actionReceived = false;
 
-  MozLoopService.promiseRegisteredWithServers().then(() => {
-    let opened = 0;
-    let windowId;
-    Chat.open = function(contentWindow, origin, title, url) {
-      opened++;
-      windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
-    };
+  yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.GUEST);
+  yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA);
+
+  let opened = 0;
+  let windowId;
+  Chat.open = function(contentWindow, origin, title, url) {
+    opened++;
+    windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
+  };
 
-    mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
-    mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
+  mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
+  mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
 
-    waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
-      do_check_true(opened === 1, "should open only one chat window");
-      do_check_true(actionReceived, "should respond with busy/reject to second call");
-      LoopCalls.clearCallInProgress(windowId);
-      run_next_test();
-    }, () => {
-      do_throw("should have opened a chat window for first call and rejected second call");
-    });
-
+  yield waitForCondition(() => { return actionReceived && opened > 0; }).then(() => {
+    do_check_true(opened === 1, "should open only one chat window");
+    do_check_true(actionReceived, "should respond with busy/reject to second call");
+    LoopCalls.clearCallInProgress(windowId);
+  }, () => {
+    do_throw("should have opened a chat window for first call and rejected second call");
   });
 });
 
 function run_test() {
   setupFakeLoopServer();
 
   // Setup fake login state so we get FxA requests.
   const MozLoopServiceInternal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).MozLoopServiceInternal;
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -560,35 +560,58 @@
           ), 
 
           Section({name: "StandaloneRoomView"}, 
             Example({summary: "Standalone room conversation (ready)"}, 
               React.DOM.div({className: "standalone"}, 
                 StandaloneRoomView({
                   dispatcher: dispatcher, 
                   activeRoomStore: activeRoomStore, 
-                  roomState: ROOM_STATES.READY})
+                  roomState: ROOM_STATES.READY, 
+                  helper: {isFirefox: returnTrue}})
               )
             ), 
 
             Example({summary: "Standalone room conversation (joined)"}, 
               React.DOM.div({className: "standalone"}, 
                 StandaloneRoomView({
                   dispatcher: dispatcher, 
                   activeRoomStore: activeRoomStore, 
-                  roomState: ROOM_STATES.JOINED})
+                  roomState: ROOM_STATES.JOINED, 
+                  helper: {isFirefox: returnTrue}})
               )
             ), 
 
             Example({summary: "Standalone room conversation (has-participants)"}, 
               React.DOM.div({className: "standalone"}, 
                 StandaloneRoomView({
                   dispatcher: dispatcher, 
                   activeRoomStore: activeRoomStore, 
-                  roomState: ROOM_STATES.HAS_PARTICIPANTS})
+                  roomState: ROOM_STATES.HAS_PARTICIPANTS, 
+                  helper: {isFirefox: returnTrue}})
+              )
+            ), 
+
+            Example({summary: "Standalone room conversation (full - FFx user)"}, 
+              React.DOM.div({className: "standalone"}, 
+                StandaloneRoomView({
+                  dispatcher: dispatcher, 
+                  activeRoomStore: activeRoomStore, 
+                  roomState: ROOM_STATES.FULL, 
+                  helper: {isFirefox: returnTrue}})
+              )
+            ), 
+
+            Example({summary: "Standalone room conversation (full - non FFx user)"}, 
+              React.DOM.div({className: "standalone"}, 
+                StandaloneRoomView({
+                  dispatcher: dispatcher, 
+                  activeRoomStore: activeRoomStore, 
+                  roomState: ROOM_STATES.FULL, 
+                  helper: {isFirefox: returnFalse}})
               )
             )
           ), 
 
           Section({name: "SVG icons preview"}, 
             Example({summary: "16x16"}, 
               SVGIcons(null)
             )
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -560,35 +560,58 @@
           </Section>
 
           <Section name="StandaloneRoomView">
             <Example summary="Standalone room conversation (ready)">
               <div className="standalone">
                 <StandaloneRoomView
                   dispatcher={dispatcher}
                   activeRoomStore={activeRoomStore}
-                  roomState={ROOM_STATES.READY} />
+                  roomState={ROOM_STATES.READY}
+                  helper={{isFirefox: returnTrue}} />
               </div>
             </Example>
 
             <Example summary="Standalone room conversation (joined)">
               <div className="standalone">
                 <StandaloneRoomView
                   dispatcher={dispatcher}
                   activeRoomStore={activeRoomStore}
-                  roomState={ROOM_STATES.JOINED} />
+                  roomState={ROOM_STATES.JOINED}
+                  helper={{isFirefox: returnTrue}} />
               </div>
             </Example>
 
             <Example summary="Standalone room conversation (has-participants)">
               <div className="standalone">
                 <StandaloneRoomView
                   dispatcher={dispatcher}
                   activeRoomStore={activeRoomStore}
-                  roomState={ROOM_STATES.HAS_PARTICIPANTS} />
+                  roomState={ROOM_STATES.HAS_PARTICIPANTS}
+                  helper={{isFirefox: returnTrue}} />
+              </div>
+            </Example>
+
+            <Example summary="Standalone room conversation (full - FFx user)">
+              <div className="standalone">
+                <StandaloneRoomView
+                  dispatcher={dispatcher}
+                  activeRoomStore={activeRoomStore}
+                  roomState={ROOM_STATES.FULL}
+                  helper={{isFirefox: returnTrue}} />
+              </div>
+            </Example>
+
+            <Example summary="Standalone room conversation (full - non FFx user)">
+              <div className="standalone">
+                <StandaloneRoomView
+                  dispatcher={dispatcher}
+                  activeRoomStore={activeRoomStore}
+                  roomState={ROOM_STATES.FULL}
+                  helper={{isFirefox: returnFalse}} />
               </div>
             </Example>
           </Section>
 
           <Section name="SVG icons preview">
             <Example summary="16x16">
               <SVGIcons />
             </Example>
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -17,16 +17,18 @@ Cu.import("resource://gre/modules/Places
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+                                  "resource:///modules/RecentWindow.jsm");
 
 // PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter.
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
                                   "resource://gre/modules/PlacesTransactions.jsm");
 
 #ifdef MOZ_SERVICES_CLOUDSYNC
@@ -468,17 +470,17 @@ this.PlacesUIUtils = {
     let features =
       "centerscreen,chrome,modal,resizable=" + (hasFolderPicker ? "yes" : "no");
 
     aParentWindow.openDialog(dialogURL, "",  features, aInfo);
     return ("performed" in aInfo && aInfo.performed);
   },
 
   _getTopBrowserWin: function PUIU__getTopBrowserWin() {
-    return Services.wm.getMostRecentWindow("navigator:browser");
+    return RecentWindow.getMostRecentBrowserWindow();
   },
 
   /**
    * Returns the closet ancestor places view for the given DOM node
    * @param aNode
    *        a DOM node
    * @return the closet ancestor places view if exists, null otherwsie.
    */
@@ -614,16 +616,20 @@ this.PlacesUIUtils = {
     // livemark.
     if (aNode.itemId == -1) {
       // Rather than executing a db query, checking the existence of the feedURI
       // annotation, detect livemark children by the fact that they are the only
       // direct non-bookmark children of bookmark folders.
       return !PlacesUtils.nodeIsFolder(parentNode);
     }
 
+    // Generally it's always possible to remove children of a query.
+    if (PlacesUtils.nodeIsQuery(parentNode))
+      return true;
+
     // Otherwise it has to be a child of an editable folder.
     return !this.isContentsReadOnly(parentNode);
   },
 
   /**
    * DO NOT USE THIS API IN ADDONS. IT IS VERY LIKELY TO CHANGE WHEN THE SWITCH
    * TO GUIDS IS COMPLETE (BUG 1071511).
    *
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -144,29 +144,30 @@ PlacesController.prototype = {
       return PlacesTransactions.topUndoEntry != null;
     case "cmd_redo":
       if (!PlacesUIUtils.useAsyncTransactions)
         return PlacesUtils.transactionManager.numberOfRedoItems > 0;
 
       return PlacesTransactions.topRedoEntry != null;
     case "cmd_cut":
     case "placesCmd_cut":
-      var nodes = this._view.selectedNodes;
-      // If selection includes history nodes there's no reason to allow cut.
-      for (var i = 0; i < nodes.length; i++) {
-        if (nodes[i].itemId == -1)
+    case "placesCmd_moveBookmarks":
+      for (let node of this._view.selectedNodes) {
+        // If selection includes history nodes or tags-as-bookmark, disallow
+        // cutting.
+        if (node.itemId == -1 ||
+            (node.parent && PlacesUtils.nodeIsTagQuery(node.parent))) {
           return false;
+        }
       }
-      // Otherwise fallback to cmd_delete check.
+      // Otherwise fall through the cmd_delete check.
     case "cmd_delete":
     case "placesCmd_delete":
     case "placesCmd_deleteDataHost":
-      return this._hasRemovableSelection(false);
-    case "placesCmd_moveBookmarks":
-      return this._hasRemovableSelection(true);
+      return this._hasRemovableSelection();
     case "cmd_copy":
     case "placesCmd_copy":
       return this._view.hasSelection;
     case "cmd_paste":
     case "placesCmd_paste":
       return this._canInsert(true) && this._isClipboardDataPasteable();
     case "cmd_selectAll":
       if (this._view.selType != "single") {
@@ -305,23 +306,21 @@ PlacesController.prototype = {
 
 
   /**
    * Determine whether or not the selection can be removed, either by the
    * delete or cut operations based on whether or not any of its contents
    * are non-removable. We don't need to worry about recursion here since it
    * is a policy decision that a removable item not be placed inside a non-
    * removable item.
-   * @param aIsMoveCommand
-   *        True if the command for which this method is called only moves the
-   *        selected items to another container, false otherwise.
+   *
    * @return true if all nodes in the selection can be removed,
    *         false otherwise.
    */
-  _hasRemovableSelection: function PC__hasRemovableSelection(aIsMoveCommand) {
+  _hasRemovableSelection() {
     var ranges = this._view.removableSelectionRanges;
     if (!ranges.length)
       return false;
 
     var root = this._view.result.root;
 
     for (var j = 0; j < ranges.length; j++) {
       var nodes = ranges[j];
@@ -1019,17 +1018,17 @@ PlacesController.prototype = {
 
   /**
    * Removes the selection
    * @param   aTxnName
    *          A name for the transaction if this is being performed
    *          as part of another operation.
    */
   remove: Task.async(function* (aTxnName) {
-    if (!this._hasRemovableSelection(false))
+    if (!this._hasRemovableSelection())
       return;
 
     NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
 
     var root = this._view.result.root;
 
     if (PlacesUtils.nodeIsFolder(root)) {
       if (PlacesUIUtils.useAsyncTransactions)
@@ -1540,17 +1539,17 @@ let PlacesControllerDragHelper = {
    *
    * @param   aUnwrappedNode
    *          A node unwrapped by PlacesUtils.unwrapNodes().
    * @return True if the node can be moved, false otherwise.
    */
   canMoveUnwrappedNode: function (aUnwrappedNode) {
     return aUnwrappedNode.id > 0 &&
            !PlacesUtils.isRootItem(aUnwrappedNode.id) &&
-           !PlacesUIUtils.isContentsReadOnly(aUnwrappedNode.parent) ||
+           (!aUnwrappedNode.parent || !PlacesUIUtils.isContentsReadOnly(aUnwrappedNode.parent)) &&
            aUnwrappedNode.parent != PlacesUtils.tagsFolderId &&
            aUnwrappedNode.grandParentId != PlacesUtils.tagsFolderId;
   },
 
   /**
    * Determines if a node can be moved.
    *
    * @param   aNode
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -26,17 +26,17 @@ skip-if = e10s # Bug ?????? - clipboard 
 [browser_history_sidebar_search.js]
 [browser_bookmarksProperties.js]
 skip-if = e10s
 
 [browser_forgetthissite_single.js]
 # disabled for very frequent oranges - bug 551540
 skip-if = true
 
-[browser_library_left_pane_commands.js]
+[browser_library_commands.js]
 [browser_drag_bookmarks_on_toolbar.js]
 skip-if = e10s # Bug ?????? - test fails - "Number of dragged items should be the same. - Got 0, expected 1"
 [browser_library_middleclick.js]
 [browser_library_views_liveupdate.js]
 [browser_views_liveupdate.js]
 
 [browser_sidebarpanels_click.js]
 # temporarily disabled for breaking the treeview - bug 658744
--- a/browser/components/places/tests/browser/browser_423515.js
+++ b/browser/components/places/tests/browser/browser_423515.js
@@ -137,16 +137,17 @@ function test() {
   });
 
   // test that a tag container cannot be moved
   tests.push({
     populate: function() {
       // tag a uri
       this.uri = makeURI("http://foo.com");
       PlacesUtils.tagging.tagURI(this.uri, ["bar"]);
+      registerCleanupFunction(() => PlacesUtils.tagging.untagURI(this.uri, ["bar"]));
     },
     validate: function() {
       // get tag root
       var query = PlacesUtils.history.getNewQuery();
       var options = PlacesUtils.history.getNewQueryOptions();
       options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY;
       var tagsNode = PlacesUtils.history.executeQuery(query, options).root;
 
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -29,19 +29,18 @@ const ACTION_ADD = 1;
 const TYPE_FOLDER = 0;
 const TYPE_BOOKMARK = 1;
 
 const TEST_URL = "http://www.example.com/";
 
 const DIALOG_URL = "chrome://browser/content/places/bookmarkProperties.xul";
 const DIALOG_URL_MINIMAL_UI = "chrome://browser/content/places/bookmarkProperties2.xul";
 
-var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
-         getService(Ci.nsIWindowMediator);
-var win = wm.getMostRecentWindow("navigator:browser");
+Cu.import("resource:///modules/RecentWindow.jsm");
+let win = RecentWindow.getMostRecentBrowserWindow();
 var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
          getService(Ci.nsIWindowWatcher);
 
 function add_bookmark(aURI) {
   var bId = PlacesUtils.bookmarks
                        .insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
                                        aURI,
                                        PlacesUtils.bookmarks.DEFAULT_INDEX,
rename from browser/components/places/tests/browser/browser_library_left_pane_commands.js
rename to browser/components/places/tests/browser/browser_library_commands.js
--- a/browser/components/places/tests/browser/browser_library_left_pane_commands.js
+++ b/browser/components/places/tests/browser/browser_library_commands.js
@@ -3,154 +3,233 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  *  Test enabled commands in the left pane folder of the Library.
  */
 
-const TEST_URI = "http://www.mozilla.org/";
+const TEST_URI = NetUtil.newURI("http://www.mozilla.org/");
 
-var gTests = [];
-var gLibrary;
+registerCleanupFunction(function* () {
+  yield PlacesUtils.bookmarks.eraseEverything();
+  yield promiseClearHistory();
+});
 
-//------------------------------------------------------------------------------
+add_task(function* test_date_container() {
+  let library = yield promiseLibrary();
+  info("Ensure date containers under History cannot be cut but can be deleted");
+
+  yield promiseAddVisits(TEST_URI);
 
-gTests.push({
-  desc: "Bug 489351 - Date containers under History in Library cannot be deleted/cut",
-  run: function() {
-    function addVisitsCallback() {
-      // Select and open the left pane "History" query.
-      var PO = gLibrary.PlacesOrganizer;
-      PO.selectLeftPaneQuery('History');
-      isnot(PO._places.selectedNode, null, "We correctly selected History");
+  // Select and open the left pane "History" query.
+  let PO = library.PlacesOrganizer;
+
+  PO.selectLeftPaneQuery('History');
+  isnot(PO._places.selectedNode, null, "We correctly selected History");
 
-      // Check that both delete and cut commands are disabled.
-      ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
-         "Cut command is disabled");
-      ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
-         "Delete command is disabled");
-      var historyNode = PO._places.selectedNode
-                          .QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      historyNode.containerOpen = true;
+  // Check that both delete and cut commands are disabled, cause this is
+  // a child of the left pane folder.
+  ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is disabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is disabled");
+  let historyNode = PlacesUtils.asContainer(PO._places.selectedNode);
+  historyNode.containerOpen = true;
 
-      // Check that we have a child container. It is "Today" container.
-      is(historyNode.childCount, 1, "History node has one child");
-      var todayNode = historyNode.getChild(0);
-      var todayNodeExpectedTitle = PlacesUtils.getString("finduri-AgeInDays-is-0");
-      is(todayNode.title, todayNodeExpectedTitle,
-         "History child is the expected container");
-
-      // Select "Today" container.
-      PO._places.selectNode(todayNode);
-      is(PO._places.selectedNode, todayNode,
-         "We correctly selected Today container");
-      // Check that delete command is enabled but cut command is disabled.
-      ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
-         "Cut command is disabled");
-      ok(PO._places.controller.isCommandEnabled("cmd_delete"),
-         "Delete command is enabled");
+  // Check that we have a child container. It is "Today" container.
+  is(historyNode.childCount, 1, "History node has one child");
+  let todayNode = historyNode.getChild(0);
+  let todayNodeExpectedTitle = PlacesUtils.getString("finduri-AgeInDays-is-0");
+  is(todayNode.title, todayNodeExpectedTitle,
+     "History child is the expected container");
 
-      // Execute the delete command and check visit has been removed.
-      PO._places.controller.doCommand("cmd_delete");
-
-      // Test live update of "History" query.
-      is(historyNode.childCount, 0, "History node has no more children");
-
-      historyNode.containerOpen = false;
+  // Select "Today" container.
+  PO._places.selectNode(todayNode);
+  is(PO._places.selectedNode, todayNode,
+     "We correctly selected Today container");
+  // Check that delete command is enabled but cut command is disabled, cause
+  // this is an history item.
+  ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is disabled");
+  ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is enabled");
 
-      let testURI = NetUtil.newURI(TEST_URI);
-      PlacesUtils.asyncHistory.isURIVisited(testURI, function(aURI, aIsVisited) {
-        ok(!aIsVisited, "Visit has been removed");
-        nextTest();
-      });
-    }
-    addVisits(
-      {uri: NetUtil.newURI(TEST_URI), visitDate: Date.now() * 1000,
-        transition: PlacesUtils.history.TRANSITION_TYPED},
-      window,
-      addVisitsCallback);
-  }
+  // Execute the delete command and check visit has been removed.
+  let promiseURIRemoved = promiseHistoryNotification("onDeleteURI",
+                                                     () => TEST_URI.equals(arguments[0]));
+  PO._places.controller.doCommand("cmd_delete");
+  yield promiseURIRemoved;
+
+  // Test live update of "History" query.
+  is(historyNode.childCount, 0, "History node has no more children");
+
+  historyNode.containerOpen = false;
+
+  ok(!(yield promiseIsURIVisited(TEST_URI)), "Visit has been removed");
+
+  library.close();
 });
 
-//------------------------------------------------------------------------------
+add_task(function* test_query_on_toolbar() {
+  let library = yield promiseLibrary();
+  info("Ensure queries can be cut or deleted");
+
+  // Select and open the left pane "Bookmarks Toolbar" folder.
+  let PO = library.PlacesOrganizer;
 
-gTests.push({
-  desc: "Bug 490156 - Can't delete smart bookmark containers",
-  run: function() {
-    // Select and open the left pane "Bookmarks Toolbar" folder.
-    var PO = gLibrary.PlacesOrganizer;
-    PO.selectLeftPaneQuery('BookmarksToolbar');
-    isnot(PO._places.selectedNode, null, "We have a valid selection");
-    is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
-       PlacesUtils.toolbarFolderId,
-       "We have correctly selected bookmarks toolbar node.");
+  PO.selectLeftPaneQuery('BookmarksToolbar');
+  isnot(PO._places.selectedNode, null, "We have a valid selection");
+  is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
+     PlacesUtils.toolbarFolderId,
+     "We have correctly selected bookmarks toolbar node.");
 
-    // Check that both cut and delete commands are disabled.
-    ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
-       "Cut command is disabled");
-    ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
-       "Delete command is disabled");
+  // Check that both cut and delete commands are disabled, cause this is a child
+  // of AllBookmarksFolderId.
+  ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is disabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is disabled");
 
-    var toolbarNode = PO._places.selectedNode
-                        .QueryInterface(Ci.nsINavHistoryContainerResultNode);
-    toolbarNode.containerOpen = true;
+  let toolbarNode = PlacesUtils.asContainer(PO._places.selectedNode);
+  toolbarNode.containerOpen = true;
 
-    // Add an History query to the toolbar.
-    PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
-                                         NetUtil.newURI("place:sort=4"),
-                                         0, // Insert at start.
-                                         "special_query");
-    // Get first child and check it is the "Most Visited" smart bookmark.
-    ok(toolbarNode.childCount > 0, "Toolbar node has children");
-    var queryNode = toolbarNode.getChild(0);
-    is(queryNode.title, "special_query", "Query node is correctly selected");
+  // Add an History query to the toolbar.
+  let query = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+                                                   url: "place:sort=4",
+                                                   title: "special_query",
+                                                   parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+                                                   index: 0 });
 
-    // Select query node.
-    PO._places.selectNode(queryNode);
-    is(PO._places.selectedNode, queryNode, "We correctly selected query node");
+  // Get first child and check it is the just inserted query.
+  ok(toolbarNode.childCount > 0, "Toolbar node has children");
+  let queryNode = toolbarNode.getChild(0);
+  is(queryNode.title, "special_query", "Query node is correctly selected");
+
+  // Select query node.
+  PO._places.selectNode(queryNode);
+  is(PO._places.selectedNode, queryNode, "We correctly selected query node");
 
-    // Check that both cut and delete commands are enabled.
-    ok(PO._places.controller.isCommandEnabled("cmd_cut"),
-       "Cut command is enabled");
-    ok(PO._places.controller.isCommandEnabled("cmd_delete"),
-       "Delete command is enabled");
+  // Check that both cut and delete commands are enabled.
+  ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(PO._places.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is enabled");
+  ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is enabled");
 
-    // Execute the delete command and check bookmark has been removed.
-    PO._places.controller.doCommand("cmd_delete");
-    try {
-      PlacesUtils.bookmarks.getFolderIdForItem(queryNode.itemId);  
-      ok(false, "Unable to remove query node bookmark");
-    } catch(ex) {
-      ok(true, "Query node bookmark has been correctly removed");
-    }
+  // Execute the delete command and check bookmark has been removed.
+  let promiseItemRemoved = promiseBookmarksNotification("onItemRemoved",
+                                                        () => query.guid == arguments[5]);
+  PO._places.controller.doCommand("cmd_delete");
+  yield promiseItemRemoved;
 
-    toolbarNode.containerOpen = false;
-    nextTest();
-  }
+  is((yield PlacesUtils.bookmarks.fetch(query.guid)), null,
+     "Query node bookmark has been correctly removed");
+
+  toolbarNode.containerOpen = false;
+
+  library.close();
 });
 
-//------------------------------------------------------------------------------
+add_task(function* test_search_contents() {
+  let item = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+                                                  url: "http://example.com/",
+                                                  title: "example page",
+                                                  parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+                                                  index: 0 });
+
+  let library = yield promiseLibrary();
+  info("Ensure query contents can be cut or deleted");
+
+  // Select and open the left pane "Bookmarks Toolbar" folder.
+  let PO = library.PlacesOrganizer;
+
+  PO.selectLeftPaneQuery('BookmarksToolbar');
+  isnot(PO._places.selectedNode, null, "We have a valid selection");
+  is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
+     PlacesUtils.toolbarFolderId,
+     "We have correctly selected bookmarks toolbar node.");
+
+  let searchBox = library.document.getElementById("searchFilter");
+  searchBox.value = "example";
+  library.PlacesSearchBox.search(searchBox.value);
+
+  let bookmarkNode = library.ContentTree.view.selectedNode;
+  is(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark");
+
+  // Check that both cut and delete commands are enabled.
+  ok(library.ContentTree.view.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(library.ContentTree.view.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is enabled");
+  ok(library.ContentTree.view.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is enabled");
+
+  library.close();
+});
+
+add_task(function* test_tags() {
+  let item = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+                                                  url: "http://example.com/",
+                                                  title: "example page",
+                                                  parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+                                                  index: 0 });
+  PlacesUtils.tagging.tagURI(NetUtil.newURI("http://example.com/"), ["test"]);
+
+  let library = yield promiseLibrary();
+  info("Ensure query contents can be cut or deleted");
 
-function nextTest() {
-  if (gTests.length) {
-    var test = gTests.shift();
-    info("Start of test: " + test.desc);
-    test.run();
-  }
-  else {
-    // Close Library window.
-    gLibrary.close();
-    // No need to cleanup anything, we have a correct left pane now.
-    finish();
-  }
-}
+  // Select and open the left pane "Bookmarks Toolbar" folder.
+  let PO = library.PlacesOrganizer;
+
+  PO.selectLeftPaneQuery('Tags');
+  let tagsNode = PO._places.selectedNode;
+  isnot(tagsNode, null, "We have a valid selection");
+  let tagsTitle = PlacesUtils.getString("TagsFolderTitle");
+  is(tagsNode.title, tagsTitle,
+     "Tags has been properly selected");
+
+  // Check that both cut and delete commands are disabled.
+  ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is disabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is disabled");
 
-function test() {
-  waitForExplicitFinish();
-  // Sanity checks.
-  ok(PlacesUtils, "PlacesUtils is running in chrome context");
-  ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
+  // Now select the tag.
+  PlacesUtils.asContainer(tagsNode).containerOpen = true;
+  let tag = tagsNode.getChild(0);
+  PO._places.selectNode(tag);
+  is(PO._places.selectedNode.title, "test",
+     "The created tag has been properly selected");
+
+  // Check that cut is disabled but delete is enabled.
+  ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is disabled");
+  ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is enabled");
 
-  // Open Library.
-  gLibrary = openLibrary(nextTest);
-}
+  let bookmarkNode = library.ContentTree.view.selectedNode;
+  is(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark");
+
+  // Check that both cut and delete commands are enabled.
+  ok(library.ContentTree.view.controller.isCommandEnabled("cmd_copy"),
+     "Copy command is enabled");
+  ok(!library.ContentTree.view.controller.isCommandEnabled("cmd_cut"),
+     "Cut command is disabled");
+  ok(library.ContentTree.view.controller.isCommandEnabled("cmd_delete"),
+     "Delete command is enabled");
+
+  tagsNode.containerOpen = false;
+
+  library.close();
+});
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -66,26 +66,34 @@ gTests.push({
       isnot(PO._places.selectedNode, null,
             "Correctly selected bookmarks menu node.");
       checkInfoBoxSelected(PO);
       ok(infoBoxExpanderWrapper.hidden,
          "Expander button is hidden for bookmarks menu node.");
       checkAddInfoFieldsCollapsed(PO);
 
       // open recently bookmarked node
+      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
+                                           NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
+                                                          "&folder=UNFILED_BOOKMARKS" +
+                                                          "&folder=TOOLBAR" +
+                                                          "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+                                                          "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+                                                          "&maxResults=10" +
+                                                          "&excludeQueries=1"),
+                                           0, "Recent Bookmarks");
+      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
+                                           NetUtil.newURI("http://mozilla.org/"),
+                                           1, "Mozilla");
       var menuNode = PO._places.selectedNode.
                      QueryInterface(Ci.nsINavHistoryContainerResultNode);
       menuNode.containerOpen = true;
       childNode = menuNode.getChild(0);
       isnot(childNode, null, "Bookmarks menu child node exists.");
-      var recentlyBookmarkedTitle = PlacesUIUtils.
-                                    getString("recentlyBookmarkedTitle");
-      isnot(recentlyBookmarkedTitle, null,
-            "Correctly got the recently bookmarked title locale string.");
-      is(childNode.title, recentlyBookmarkedTitle,
+      is(childNode.title, "Recent Bookmarks",
          "Correctly selected recently bookmarked node.");
       PO._places.selectNode(childNode);
       checkInfoBoxSelected(PO);
       ok(!infoBoxExpanderWrapper.hidden,
          "Expander button is not hidden for recently bookmarked node.");
       checkAddInfoFieldsNotCollapsed(PO);
 
       // open first bookmark
@@ -93,25 +101,16 @@ gTests.push({
       ok(view.rowCount > 0, "Bookmark item exists.");
       view.selection.select(0);
       checkInfoBoxSelected(PO);
       ok(!infoBoxExpanderWrapper.hidden,
          "Expander button is not hidden for bookmark item.");
       checkAddInfoFieldsNotCollapsed(PO);
       checkAddInfoFields(PO, "bookmark item");
 
-      // make sure additional fields are still hidden in second bookmark item
-      ok(view.rowCount > 1, "Second bookmark item exists.");
-      view.selection.select(1);
-      checkInfoBoxSelected(PO);
-      ok(!infoBoxExpanderWrapper.hidden,
-         "Expander button is not hidden for second bookmark item.");
-      checkAddInfoFieldsNotCollapsed(PO);
-      checkAddInfoFields(PO, "second bookmark item");
-
       menuNode.containerOpen = false;
 
       waitForClearHistory(nextTest);
     }
     // add a visit to browser history
     addVisits(
       { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
         transition: PlacesUtils.history.TRANSITION_TYPED },
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -38,17 +38,17 @@ function openLibrary(callback, aLeftPane
  * If one is opens returns itm otherwise it opens a new one.
  *
  * @param aLeftPaneRoot
  *        Hierarchy to open and select in the left pane.
  */
 function promiseLibrary(aLeftPaneRoot) {
   let deferred = Promise.defer();
   let library = Services.wm.getMostRecentWindow("Places:Organizer");
-  if (library) {
+  if (library && !library.closed) {
     if (aLeftPaneRoot)
       library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
     deferred.resolve(library);
   }
   else {
     openLibrary(aLibrary => deferred.resolve(aLibrary), aLeftPaneRoot);
   }
   return deferred.promise;
@@ -159,17 +159,17 @@ function addVisits(aPlaceInfo, aWindow, 
 
   // Create mozIVisitInfo for each entry.
   let now = Date.now();
   for (let i = 0; i < places.length; i++) {
     if (!places[i].title) {
       places[i].title = "test visit for " + places[i].uri.spec;
     }
     places[i].visits = [{
-      transitionType: places[i].transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
+      transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
                                                          : places[i].transition,
       visitDate: places[i].visitDate || (now++) * 1000,
       referrerURI: places[i].referrer
     }];
   }
 
   aWindow.PlacesUtils.asyncHistory.updatePlaces(
     places,
@@ -198,8 +198,211 @@ function synthesizeClickOnSelectedTreeCe
   // Calculate the click coordinates.
   var rect = tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text");
   var x = rect.x + rect.width / 2;
   var y = rect.y + rect.height / 2;
   // Simulate the click.
   EventUtils.synthesizeMouse(aTree.body, x, y, aOptions || {},
                              aTree.ownerDocument.defaultView);
 }
+
+/**
+ * Asynchronously adds visits to a page.
+ *
+ * @param aPlaceInfo
+ *        Can be an nsIURI, in such a case a single LINK visit will be added.
+ *        Otherwise can be an object describing the visit to add, or an array
+ *        of these objects:
+ *          { uri: nsIURI of the page,
+ *            transition: one of the TRANSITION_* from nsINavHistoryService,
+ *            [optional] title: title of the page,
+ *            [optional] visitDate: visit date in microseconds from the epoch
+ *            [optional] referrer: nsIURI of the referrer for this visit
+ *          }
+ *
+ * @return {Promise}
+ * @resolves When all visits have been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseAddVisits(aPlaceInfo)
+{
+  let deferred = Promise.defer();
+  let places = [];
+  if (aPlaceInfo instanceof Ci.nsIURI) {
+    places.push({ uri: aPlaceInfo });
+  }
+  else if (Array.isArray(aPlaceInfo)) {
+    places = places.concat(aPlaceInfo);
+  } else {
+    places.push(aPlaceInfo)
+  }
+
+  // Create mozIVisitInfo for each entry.
+  let now = Date.now();
+  for (let i = 0; i < places.length; i++) {
+    if (!places[i].title) {
+      places[i].title = "test visit for " + places[i].uri.spec;
+    }
+    places[i].visits = [{
+      transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
+                                                         : places[i].transition,
+      visitDate: places[i].visitDate || (now++) * 1000,
+      referrerURI: places[i].referrer
+    }];
+  }
+
+  PlacesUtils.asyncHistory.updatePlaces(
+    places,
+    {
+      handleError: function AAV_handleError(aResultCode, aPlaceInfo) {
+        let ex = new Components.Exception("Unexpected error in adding visits.",
+                                          aResultCode);
+        deferred.reject(ex);
+      },
+      handleResult: function () {},
+      handleCompletion: function UP_handleCompletion() {
+        deferred.resolve();
+      }
+    }
+  );
+
+  return deferred.promise;
+}
+
+/**
+ * Asynchronously check a url is visited.
+ *
+ * @param aURI The URI.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI) {
+  let deferred = Promise.defer();
+
+  PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+    deferred.resolve(aIsVisited);
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Waits for all pending async statements on the default connection.
+ *
+ * @return {Promise}
+ * @resolves When all pending async statements finished.
+ * @rejects Never.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ *       a write lock.  Since all statements on the same connection are
+ *       serialized, the end of this write operation means that all writes are
+ *       complete.  Note that WAL makes so that writers don't block readers, but
+ *       this is a problem only across different connections.
+ */
+function promiseAsyncUpdates()
+{
+  let deferred = Promise.defer();
+
+  let db = DBConn();
+  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+  begin.executeAsync();
+  begin.finalize();
+
+  let commit = db.createAsyncStatement("COMMIT");
+  commit.executeAsync({
+    handleResult: function () {},
+    handleError: function () {},
+    handleCompletion: function(aReason)
+    {
+      deferred.resolve();
+    }
+  });
+  commit.finalize();
+
+  return deferred.promise;
+}
+
+function promiseBookmarksNotification(notification, conditionFn) {
+  info(`Waiting for ${notification}`);
+  return new Promise((resolve, reject) => {
+    let proxifiedObserver = new Proxy({}, {
+      get: (target, name) => {
+        if (name == "QueryInterface")
+          return XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]);
+        if (name == notification)
+          return () => {
+            if (conditionFn.apply(this, arguments)) {
+              clearTimeout(timeout);
+              PlacesUtils.bookmarks.removeObserver(proxifiedObserver, false);
+              executeSoon(resolve);
+            }
+          }
+        return () => {};
+      }
+    });
+    PlacesUtils.bookmarks.addObserver(proxifiedObserver, false);
+    let timeout = setTimeout(() => {
+      PlacesUtils.bookmarks.removeObserver(proxifiedObserver, false);
+      reject(new Error("Timed out while waiting for bookmarks notification"));
+    }, 2000);
+  });
+}
+
+function promiseHistoryNotification(notification, conditionFn) {
+  info(`Waiting for ${notification}`);
+  return new Promise((resolve, reject) => {
+    let proxifiedObserver = new Proxy({}, {
+      get: (target, name) => {
+        if (name == "QueryInterface")
+          return XPCOMUtils.generateQI([ Ci.nsINavHistoryObserver ]);
+        if (name == notification)
+          return () => {
+            if (conditionFn.apply(this, arguments)) {
+              clearTimeout(timeout);
+              PlacesUtils.history.removeObserver(proxifiedObserver, false);
+              executeSoon(resolve);
+            }
+          }
+        return () => {};
+      }
+    });
+    PlacesUtils.history.addObserver(proxifiedObserver, false);
+    let timeout = setTimeout(() => {
+      PlacesUtils.history.removeObserver(proxifiedObserver, false);
+      reject(new Error("Timed out while waiting for history notification"));
+    }, 2000);
+  });
+}
+
+/**
+ * Clears history asynchronously.
+ *
+ * @return {Promise}
+ * @resolves When history has been cleared.
+ * @rejects Never.
+ */
+function promiseClearHistory() {
+  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+  PlacesUtils.bhistory.removeAllPages();
+  return promise;
+}
+
+/**
+ * Allows waiting for an observer notification once.
+ *
+ * @param topic
+ *        Notification topic to observe.
+ *
+ * @return {Promise}
+ * @resolves The array [subject, data] from the observed notification.
+ * @rejects Never.
+ */
+function promiseTopicObserved(topic)
+{
+  let deferred = Promise.defer();
+  info("Waiting for observer topic " + topic);
+  Services.obs.addObserver(function PTO_observe(subject, topic, data) {
+    Services.obs.removeObserver(PTO_observe, topic);
+    deferred.resolve([subject, data]);
+  }, topic, false);
+  return deferred.promise;
+}
--- a/browser/devtools/debugger/test/code_script-switching-02.js
+++ b/browser/devtools/debugger/test/code_script-switching-02.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function secondCall() {
   // This comment is useful: ☺
   eval("debugger;");
   function foo() {}
-  if (true) {
+  if (x) {
     foo();
   }
 }
+
+var x = true;
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -19,17 +19,19 @@ support-files =
 [browser_graphs-02.js]
 [browser_graphs-03.js]
 [browser_graphs-04.js]
 [browser_graphs-05.js]
 [browser_graphs-06.js]
 [browser_graphs-07a.js]
 [browser_graphs-07b.js]
 [browser_graphs-08.js]
-[browser_graphs-09.js]
+[browser_graphs-09a.js]
+[browser_graphs-09b.js]
+[browser_graphs-09c.js]
 [browser_graphs-10a.js]
 [browser_graphs-10b.js]
 [browser_graphs-11a.js]
 [browser_graphs-11b.js]
 [browser_graphs-12.js]
 [browser_graphs-13.js]
 [browser_graphs-14.js]
 [browser_inplace-editor.js]
rename from browser/devtools/shared/test/browser_graphs-09.js
rename to browser/devtools/shared/test/browser_graphs-09a.js
--- a/browser/devtools/shared/test/browser_graphs-09.js
+++ b/browser/devtools/shared/test/browser_graphs-09a.js
@@ -22,31 +22,47 @@ function* performTest() {
 
   yield testGraph(graph);
 
   graph.destroy();
   host.destroy();
 }
 
 function* testGraph(graph) {
-  info("Should be able to set the grpah data before waiting for the ready event.");
+  info("Should be able to set the graph data before waiting for the ready event.");
 
   yield graph.setDataWhenReady(TEST_DATA);
   ok(graph.hasData(), "Data was set successfully.");
 
+  is(graph._gutter.hidden, false,
+    "The gutter should not be hidden because the tooltips have arrows.");
+  is(graph._maxTooltip.hidden, false,
+    "The max tooltip should not be hidden.");
+  is(graph._avgTooltip.hidden, false,
+    "The avg tooltip should not be hidden.");
+  is(graph._minTooltip.hidden, false,
+    "The min tooltip should not be hidden.");
+
+  is(graph._maxTooltip.getAttribute("with-arrows"), "true",
+    "The maximum tooltip has the correct 'with-arrows' attribute.");
+  is(graph._avgTooltip.getAttribute("with-arrows"), "true",
+    "The average tooltip has the correct 'with-arrows' attribute.");
+  is(graph._minTooltip.getAttribute("with-arrows"), "true",
+    "The minimum tooltip has the correct 'with-arrows' attribute.");
+
   is(graph._maxTooltip.querySelector("[text=info]").textContent, "max",
     "The maximum tooltip displays the correct info.");
   is(graph._avgTooltip.querySelector("[text=info]").textContent, "avg",
     "The average tooltip displays the correct info.");
   is(graph._minTooltip.querySelector("[text=info]").textContent, "min",
     "The minimum tooltip displays the correct info.");
 
   is(graph._maxTooltip.querySelector("[text=value]").textContent, "60",
     "The maximum tooltip displays the correct value.");
-  is(graph._avgTooltip.querySelector("[text=value]").textContent, "41",
+  is(graph._avgTooltip.querySelector("[text=value]").textContent, "41.71",
     "The average tooltip displays the correct value.");
   is(graph._minTooltip.querySelector("[text=value]").textContent, "10",
     "The minimum tooltip displays the correct value.");
 
   is(graph._maxTooltip.querySelector("[text=metric]").textContent, "fps",
     "The maximum tooltip displays the correct metric.");
   is(graph._avgTooltip.querySelector("[text=metric]").textContent, "fps",
     "The average tooltip displays the correct metric.");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-09b.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that line graphs properly use the tooltips configuration properties.
+
+const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
+let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  let graph = new LineGraphWidget(doc.body, "fps");
+  graph.withTooltipArrows = false;
+  graph.withFixedTooltipPositions = true;
+
+  yield testGraph(graph);
+
+  graph.destroy();
+  host.destroy();
+}
+
+function* testGraph(graph) {
+  yield graph.setDataWhenReady(TEST_DATA);
+
+  is(graph._gutter.hidden, true,
+    "The gutter should be hidden because the tooltips don't have arrows.");
+  is(graph._maxTooltip.hidden, false,
+    "The max tooltip should not be hidden.");
+  is(graph._avgTooltip.hidden, false,
+    "The avg tooltip should not be hidden.");
+  is(graph._minTooltip.hidden, false,
+    "The min tooltip should not be hidden.");
+
+  is(graph._maxTooltip.getAttribute("with-arrows"), "false",
+    "The maximum tooltip has the correct 'with-arrows' attribute.");
+  is(graph._avgTooltip.getAttribute("with-arrows"), "false",
+    "The average tooltip has the correct 'with-arrows' attribute.");
+  is(graph._minTooltip.getAttribute("with-arrows"), "false",
+    "The minimum tooltip has the correct 'with-arrows' attribute.");
+
+  is(parseInt(graph._maxTooltip.style.top), 8,
+    "The maximum tooltip is positioned correctly.");
+  is(parseInt(graph._avgTooltip.style.top), 8,
+    "The average tooltip is positioned correctly.");
+  is(parseInt(graph._minTooltip.style.top), 142,
+    "The minimum tooltip is positioned correctly.");
+
+  is(parseInt(graph._maxGutterLine.style.top), 22,
+    "The maximum gutter line is positioned correctly.");
+  is(parseInt(graph._avgGutterLine.style.top), 61,
+    "The average gutter line is positioned correctly.");
+  is(parseInt(graph._minGutterLine.style.top), 128,
+    "The minimum gutter line is positioned correctly.");
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-09c.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that line graphs hide the tooltips when there's no data available.
+
+const TEST_DATA = [];
+let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  let graph = new LineGraphWidget(doc.body, "fps");
+
+  yield testGraph(graph);
+
+  graph.destroy();
+  host.destroy();
+}
+
+function* testGraph(graph) {
+  yield graph.setDataWhenReady(TEST_DATA);
+
+  is(graph._gutter.hidden, false,
+    "The gutter should not be hidden.");
+  is(graph._maxTooltip.hidden, true,
+    "The max tooltip should be hidden.");
+  is(graph._avgTooltip.hidden, true,
+    "The avg tooltip should be hidden.");
+  is(graph._minTooltip.hidden, true,
+    "The min tooltip should be hidden.");
+}
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -14,16 +14,17 @@ this.EXPORTED_SYMBOLS = [
   "AbstractCanvasGraph",
   "LineGraphWidget",
   "BarGraphWidget",
   "CanvasGraphUtils"
 ];
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
+const L10N = new ViewHelpers.L10N();
 
 // Generic constants.
 
 const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
 const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00075;
 const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.1;
 const GRAPH_WHEEL_MIN_SELECTION_WIDTH = 10; // px
 
@@ -39,18 +40,19 @@ const GRAPH_STRIPE_PATTERN_WIDTH = 16; /
 const GRAPH_STRIPE_PATTERN_HEIGHT = 16; // px
 const GRAPH_STRIPE_PATTERN_LINE_WIDTH = 2; // px
 const GRAPH_STRIPE_PATTERN_LINE_SPACING = 4; // px
 
 // Line graph constants.
 
 const LINE_GRAPH_DAMPEN_VALUES = 0.85;
 const LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS = 400; // 20 px
-const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 10; // px
+const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
 
+const LINE_GRAPH_BACKGROUND_COLOR = "#0088cc";
 const LINE_GRAPH_STROKE_WIDTH = 1; // px
 const LINE_GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
 const LINE_GRAPH_HELPER_LINES_DASH = [5]; // px
 const LINE_GRAPH_HELPER_LINES_WIDTH = 1; // px
 const LINE_GRAPH_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
 const LINE_GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
 const LINE_GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
 const LINE_GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
@@ -482,26 +484,28 @@ AbstractCanvasGraph.prototype = {
     this.emit("deselecting");
   },
 
   /**
    * Gets whether or not this graph has a selection.
    * @return boolean
    */
   hasSelection: function() {
-    return this._selection.start != null && this._selection.end != null;
+    return this._selection &&
+      this._selection.start != null && this._selection.end != null;
   },
 
   /**
    * Gets whether or not a selection is currently being made, for example
    * via a click+drag operation.
    * @return boolean
    */
   hasSelectionInProgress: function() {
-    return this._selection.start != null && this._selection.end == null;
+    return this._selection &&
+      this._selection.start != null && this._selection.end == null;
   },
 
   /**
    * Specifies whether or not mouse selection is allowed.
    * @type boolean
    */
   selectionEnabled: true,
 
@@ -547,17 +551,17 @@ AbstractCanvasGraph.prototype = {
     this._shouldRedraw = true;
   },
 
   /**
    * Gets whether or not this graph has a visible cursor.
    * @return boolean
    */
   hasCursor: function() {
-    return this._cursor.x != null;
+    return this._cursor && this._cursor.x != null;
   },
 
   /**
    * Specifies if this graph's selection is different from another one.
    *
    * @param object other
    *        The other graph's selection, as { start, end } values.
    */
@@ -1171,76 +1175,104 @@ this.LineGraphWidget = function(parent, 
     this._minGutterLine = this._createGutterLine("minimum");
     this._maxTooltip = this._createTooltip("maximum", "start", "max", metric);
     this._avgTooltip = this._createTooltip("average", "end", "avg", metric);
     this._minTooltip = this._createTooltip("minimum", "start", "min", metric);
   });
 }
 
 LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
+  backgroundColor: LINE_GRAPH_BACKGROUND_COLOR,
+  backgroundGradientStart: LINE_GRAPH_BACKGROUND_GRADIENT_START,
+  backgroundGradientEnd: LINE_GRAPH_BACKGROUND_GRADIENT_END,
+  strokeColor: LINE_GRAPH_STROKE_COLOR,
+  strokeWidth: LINE_GRAPH_STROKE_WIDTH,
+  maximumLineColor: LINE_GRAPH_MAXIMUM_LINE_COLOR,
+  averageLineColor: LINE_GRAPH_AVERAGE_LINE_COLOR,
+  minimumLineColor: LINE_GRAPH_MINIMUM_LINE_COLOR,
   clipheadLineColor: LINE_GRAPH_CLIPHEAD_LINE_COLOR,
   selectionLineColor: LINE_GRAPH_SELECTION_LINE_COLOR,
   selectionBackgroundColor: LINE_GRAPH_SELECTION_BACKGROUND_COLOR,
   selectionStripesColor: LINE_GRAPH_SELECTION_STRIPES_COLOR,
   regionBackgroundColor: LINE_GRAPH_REGION_BACKGROUND_COLOR,
   regionStripesColor: LINE_GRAPH_REGION_STRIPES_COLOR,
 
   /**
    * Optionally offsets the `delta` in the data source by this scalar.
    */
   dataOffsetX: 0,
 
   /**
+   * The scalar used to multiply the graph values to leave some headroom
+   * on the top.
+   */
+  dampenValuesFactor: LINE_GRAPH_DAMPEN_VALUES,
+
+  /**
    * Points that are too close too each other in the graph will not be rendered.
    * This scalar specifies the required minimum squared distance between points.
    */
   minDistanceBetweenPoints: LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS,
 
   /**
+   * Specifies if min/max/avg tooltips have arrow handlers on their sides.
+   */
+  withTooltipArrows: true,
+
+  /**
+   * Specifies if min/max/avg tooltips are positioned based on the actual
+   * values, or just placed next to the graph corners.
+   */
+  withFixedTooltipPositions: false,
+
+  /**
    * Renders the graph's data source.
    * @see AbstractCanvasGraph.prototype.buildGraphImage
    */
   buildGraphImage: function() {
     let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
     let width = this._width;
     let height = this._height;
 
     let totalTicks = this._data.length;
-    let firstTick = this._data[0].delta;
-    let lastTick = this._data[totalTicks - 1].delta;
+    let firstTick = totalTicks ? this._data[0].delta : 0;
+    let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
     let maxValue = Number.MIN_SAFE_INTEGER;
     let minValue = Number.MAX_SAFE_INTEGER;
     let sumValues = 0;
 
     for (let { delta, value } of this._data) {
       maxValue = Math.max(value, maxValue);
       minValue = Math.min(value, minValue);
       sumValues += value;
     }
 
     let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
-    let dataScaleY = this.dataScaleY = height / maxValue * LINE_GRAPH_DAMPEN_VALUES;
+    let dataScaleY = this.dataScaleY = height / maxValue * this.dampenValuesFactor;
 
     /**
      * Calculates the squared distance between two 2D points.
      */
     function distSquared(x0, y0, x1, y1) {
       let xs = x1 - x0;
       let ys = y1 - y0;
       return xs * xs + ys * ys;
     }
 
     // Draw the graph.
 
+    ctx.fillStyle = this.backgroundColor;
+    ctx.fillRect(0, 0, width, height);
+
     let gradient = ctx.createLinearGradient(0, height / 2, 0, height);
-    gradient.addColorStop(0, LINE_GRAPH_BACKGROUND_GRADIENT_START);
-    gradient.addColorStop(1, LINE_GRAPH_BACKGROUND_GRADIENT_END);
+    gradient.addColorStop(0, this.backgroundGradientStart);
+    gradient.addColorStop(1, this.backgroundGradientEnd);
     ctx.fillStyle = gradient;
-    ctx.strokeStyle = LINE_GRAPH_STROKE_COLOR;
-    ctx.lineWidth = LINE_GRAPH_STROKE_WIDTH * this._pixelRatio;
+    ctx.strokeStyle = this.strokeColor;
+    ctx.lineWidth = this.strokeWidth * this._pixelRatio;
     ctx.beginPath();
 
     let prevX = 0;
     let prevY = 0;
 
     for (let { delta, value } of this._data) {
       let currX = (delta - this.dataOffsetX) * dataScaleX;
       let currY = height - value * dataScaleY;
@@ -1263,77 +1295,93 @@ LineGraphWidget.prototype = Heritage.ext
       }
     }
 
     ctx.fill();
     ctx.stroke();
 
     // Draw the maximum value horizontal line.
 
-    ctx.strokeStyle = LINE_GRAPH_MAXIMUM_LINE_COLOR;
+    ctx.strokeStyle = this.maximumLineColor;
     ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
     ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
     ctx.beginPath();
-    let maximumY = height - maxValue * dataScaleY - ctx.lineWidth;
+    let maximumY = height - maxValue * dataScaleY;
     ctx.moveTo(0, maximumY);
     ctx.lineTo(width, maximumY);
     ctx.stroke();
 
     // Draw the average value horizontal line.
 
-    ctx.strokeStyle = LINE_GRAPH_AVERAGE_LINE_COLOR;
+    ctx.strokeStyle = this.averageLineColor;
     ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
     ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
     ctx.beginPath();
-    let avgValue = sumValues / totalTicks;
-    let averageY = height - avgValue * dataScaleY - ctx.lineWidth;
+    let avgValue = totalTicks ? sumValues / totalTicks : 0;
+    let averageY = height - avgValue * dataScaleY;
     ctx.moveTo(0, averageY);
     ctx.lineTo(width, averageY);
     ctx.stroke();
 
     // Draw the minimum value horizontal line.
 
-    ctx.strokeStyle = LINE_GRAPH_MINIMUM_LINE_COLOR;
+    ctx.strokeStyle = this.minimumLineColor;
     ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
     ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
     ctx.beginPath();
-    let minimumY = height - minValue * dataScaleY - ctx.lineWidth;
+    let minimumY = height - minValue * dataScaleY;
     ctx.moveTo(0, minimumY);
     ctx.lineTo(width, minimumY);
     ctx.stroke();
 
     // Update the tooltips text and gutter lines.
 
-    this._maxTooltip.querySelector("[text=value]").textContent = maxValue|0;
-    this._avgTooltip.querySelector("[text=value]").textContent = avgValue|0;
-    this._minTooltip.querySelector("[text=value]").textContent = minValue|0;
+    this._maxTooltip.querySelector("[text=value]").textContent =
+      L10N.numberWithDecimals(maxValue, 2);
+    this._avgTooltip.querySelector("[text=value]").textContent =
+      L10N.numberWithDecimals(avgValue, 2);
+    this._minTooltip.querySelector("[text=value]").textContent =
+      L10N.numberWithDecimals(minValue, 2);
 
     /**
      * Constrains a value to a range.
      */
     function clamp(value, min, max) {
       if (value < min) return min;
       if (value > max) return max;
       return value;
     }
 
     let bottom = height / this._pixelRatio;
-    let maxPosY = map(maxValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
-    let avgPosY = map(avgValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
-    let minPosY = map(minValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
+    let maxPosY = map(maxValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
+    let avgPosY = map(avgValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
+    let minPosY = map(minValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
 
     let safeTop = LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
     let safeBottom = bottom - LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
 
-    this._maxTooltip.style.top = clamp(maxPosY, safeTop, safeBottom) + "px";
-    this._avgTooltip.style.top = clamp(avgPosY, safeTop, safeBottom) + "px";
-    this._minTooltip.style.top = clamp(minPosY, safeTop, safeBottom) + "px";
-    this._maxGutterLine.style.top = clamp(maxPosY, safeTop, safeBottom) + "px";
-    this._avgGutterLine.style.top = clamp(avgPosY, safeTop, safeBottom) + "px";
-    this._minGutterLine.style.top = clamp(minPosY, safeTop, safeBottom) + "px";
+    this._maxTooltip.style.top = (this.withFixedTooltipPositions
+      ? safeTop : clamp(maxPosY, safeTop, safeBottom)) + "px";
+    this._avgTooltip.style.top = (this.withFixedTooltipPositions
+      ? safeTop : clamp(avgPosY, safeTop, safeBottom)) + "px";
+    this._minTooltip.style.top = (this.withFixedTooltipPositions
+      ? safeBottom : clamp(minPosY, safeTop, safeBottom)) + "px";
+
+    this._maxGutterLine.style.top = maxPosY + "px";
+    this._avgGutterLine.style.top = avgPosY + "px";
+    this._minGutterLine.style.top = minPosY + "px";
+
+    this._maxTooltip.setAttribute("with-arrows", this.withTooltipArrows);
+    this._avgTooltip.setAttribute("with-arrows", this.withTooltipArrows);
+    this._minTooltip.setAttribute("with-arrows", this.withTooltipArrows);
+
+    this._gutter.hidden = !this.withTooltipArrows;
+    this._maxTooltip.hidden = !totalTicks;
+    this._avgTooltip.hidden = !totalTicks;
+    this._minTooltip.hidden = !totalTicks;
 
     return canvas;
   },
 
   /**
    * Creates the gutter node when constructing this graph.
    * @return nsIDOMNode
    */
@@ -1462,16 +1510,22 @@ BarGraphWidget.prototype = Heritage.exte
   format: null,
 
   /**
    * Optionally offsets the `delta` in the data source by this scalar.
    */
   dataOffsetX: 0,
 
   /**
+   * The scalar used to multiply the graph values to leave some headroom
+   * on the top.
+   */
+  dampenValuesFactor: BAR_GRAPH_DAMPEN_VALUES,
+
+  /**
    * Bars that are too close too each other in the graph will be combined.
    * This scalar specifies the required minimum width of each bar.
    */
   minBarsWidth: BAR_GRAPH_MIN_BARS_WIDTH,
 
   /**
    * Blocks in a bar that are too thin inside the bar will not be rendered.
    * This scalar specifies the required minimum height of each block.
@@ -1515,17 +1569,17 @@ BarGraphWidget.prototype = Heritage.exte
     let minBarsWidth = this.minBarsWidth * this._pixelRatio;
     let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
 
     let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
     let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
       data: this._data,
       dataScaleX: dataScaleX,
       minBarsWidth: minBarsWidth
-    }) * BAR_GRAPH_DAMPEN_VALUES;
+    }) * this.dampenValuesFactor;
 
     // Draw the graph.
 
     // Iterate over the blocks, then the bars, to draw all rectangles of
     // the same color in a single pass. See the @constructor for more
     // information about the data source, and how a "bar" contains "blocks".
 
     this._blocksBoundingRects = [];
@@ -1918,16 +1972,23 @@ this.CanvasGraphUtils = {
 
   /**
    * Makes sure selections in one graph are reflected in another.
    */
   linkSelection: function(graph1, graph2) {
     if (!graph1 || !graph2) {
       return;
     }
+
+    if (graph1.hasSelection()) {
+      graph2.setSelection(graph1.getSelection());
+    } else {
+      graph2.dropSelection();
+    }
+
     graph1.on("selecting", () => {
       graph2.setSelection(graph1.getSelection());
     });
     graph2.on("selecting", () => {
       graph1.setSelection(graph2.getSelection());
     });
     graph1.on("deselecting", () => {
       graph2.dropSelection();
--- a/browser/devtools/timeline/moz.build
+++ b/browser/devtools/timeline/moz.build
@@ -1,13 +1,14 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES.devtools.timeline += [
     'panel.js',
     'widgets/global.js',
-    'widgets/overview.js',
+    'widgets/markers-overview.js',
+    'widgets/memory-overview.js',
     'widgets/waterfall.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/timeline/test/browser.ini
+++ b/browser/devtools/timeline/test/browser.ini
@@ -5,12 +5,13 @@ support-files =
   head.js
 
 [browser_timeline_aaa_run_first_leaktest.js]
 [browser_timeline_blueprint.js]
 [browser_timeline_overview-initial-selection-01.js]
 [browser_timeline_overview-initial-selection-02.js]
 [browser_timeline_overview-update.js]
 [browser_timeline_panels.js]
+[browser_timeline_recording-without-memory.js]
 [browser_timeline_recording.js]
 [browser_timeline_waterfall-background.js]
 [browser_timeline_waterfall-generic.js]
 [browser_timeline_waterfall-styles.js]
--- a/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-01.js
+++ b/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-01.js
@@ -3,39 +3,45 @@
 
 /**
  * Tests if the overview has an initial selection when recording has finished
  * and there is data available.
  */
 
 let test = Task.async(function*() {
   let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { EVENTS, TimelineView, TimelineController } = panel.panelWin;
+  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
   let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
 
+  $("#memory-checkbox").checked = true;
+  yield TimelineController.updateMemoryRecording();
+
   yield TimelineController.toggleRecording();
   ok(true, "Recording has started.");
 
   let updated = 0;
   panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
 
   ok((yield waitUntil(() => updated > 10)),
     "The overview graph was updated a bunch of times.");
   ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
     "There are some markers available.");
+  ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
+    "There are some memory measurements available now.");
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has ended.");
 
+  let interval = TimelineController.getInterval();
   let markers = TimelineController.getMarkers();
-  let selection = TimelineView.overview.getSelection();
+  let selection = TimelineView.markersOverview.getSelection();
 
   is((selection.start) | 0,
-     ((markers[0].start - markers.startTime) * TimelineView.overview.dataScaleX) | 0,
+     ((markers[0].start - interval.startTime) * TimelineView.markersOverview.dataScaleX) | 0,
     "The initial selection start is correct.");
 
   is((selection.end - selection.start) | 0,
-     (selectionRatio * TimelineView.overview.width) | 0,
+     (selectionRatio * TimelineView.markersOverview.width) | 0,
     "The initial selection end is correct.");
 
   yield teardown(panel);
   finish();
 });
--- a/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-02.js
+++ b/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-02.js
@@ -3,30 +3,36 @@
 
 /**
  * Tests if the overview has no initial selection when recording has finished
  * and there is no data available.
  */
 
 let test = Task.async(function*() {
   let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { EVENTS, TimelineView, TimelineController } = panel.panelWin;
+  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
   let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
 
+  $("#memory-checkbox").checked = true;
+  yield TimelineController.updateMemoryRecording();
+
   yield TimelineController.toggleRecording();
   ok(true, "Recording has started.");
 
   yield TimelineController._stopRecordingAndDiscardData();
   ok(true, "Recording has ended.");
 
   let markers = TimelineController.getMarkers();
-  let selection = TimelineView.overview.getSelection();
+  let memory = TimelineController.getMemory();
+  let selection = TimelineView.markersOverview.getSelection();
 
   is(markers.length, 0,
     "There are no markers available.");
+  is(memory.length, 0,
+    "There are no memory measurements available.");
   is(selection.start, null,
     "The initial selection start is correct.");
   is(selection.end, null,
     "The initial selection end is correct.");
 
   yield teardown(panel);
   finish();
 });
--- a/browser/devtools/timeline/test/browser_timeline_overview-update.js
+++ b/browser/devtools/timeline/test/browser_timeline_overview-update.js
@@ -1,48 +1,74 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Tests if the overview graph is continuously updated.
+ * Tests if the markers and memory overviews are continuously updated.
  */
 
 let test = Task.async(function*() {
   let { target, panel } = yield initTimelinePanel("about:blank");
-  let { EVENTS, TimelineView, TimelineController } = panel.panelWin;
+  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
 
-  yield DevToolsUtils.waitForTime(1000);
+  $("#memory-checkbox").checked = true;
+  yield TimelineController.updateMemoryRecording();
+
   yield TimelineController.toggleRecording();
   ok(true, "Recording has started.");
 
-  ok("selectionEnabled" in TimelineView.overview,
-    "The selection should not be enabled for the overview graph (1).");
-  is(TimelineView.overview.selectionEnabled, false,
-    "The selection should not be enabled for the overview graph (2).");
-  is(TimelineView.overview.hasSelection(), false,
-    "The overview graph shouldn't have a selection before recording.");
+  ok("selectionEnabled" in TimelineView.markersOverview,
+    "The selection should not be enabled for the markers overview (1).");
+  is(TimelineView.markersOverview.selectionEnabled, false,
+    "The selection should not be enabled for the markers overview (2).");
+  is(TimelineView.markersOverview.hasSelection(), false,
+    "The markers overview shouldn't have a selection before recording.");
+
+  ok("selectionEnabled" in TimelineView.memoryOverview,
+    "The selection should not be enabled for the memory overview (1).");
+  is(TimelineView.memoryOverview.selectionEnabled, false,
+    "The selection should not be enabled for the memory overview (2).");
+  is(TimelineView.memoryOverview.hasSelection(), false,
+    "The memory overview shouldn't have a selection before recording.");
 
   let updated = 0;
   panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
 
   ok((yield waitUntil(() => updated > 10)),
-    "The overview graph was updated a bunch of times.");
+    "The overviews were updated a bunch of times.");
+  ok((yield waitUntil(() => TimelineController.getMemory().length > 10)),
+    "There are some memory measurements available now.");
 
-  ok("selectionEnabled" in TimelineView.overview,
-    "The selection should still not be enabled for the overview graph (3).");
-  is(TimelineView.overview.selectionEnabled, false,
-    "The selection should still not be enabled for the overview graph (4).");
-  is(TimelineView.overview.hasSelection(), false,
-    "The overview graph should not have a selection while recording.");
+  ok("selectionEnabled" in TimelineView.markersOverview,
+    "The selection should still not be enabled for the markers overview (3).");
+  is(TimelineView.markersOverview.selectionEnabled, false,
+    "The selection should still not be enabled for the markers overview (4).");
+  is(TimelineView.markersOverview.hasSelection(), false,
+    "The markers overview should not have a selection while recording.");
+
+  ok("selectionEnabled" in TimelineView.memoryOverview,
+    "The selection should still not be enabled for the memory overview (3).");
+  is(TimelineView.memoryOverview.selectionEnabled, false,
+    "The selection should still not be enabled for the memory overview (4).");
+  is(TimelineView.memoryOverview.hasSelection(), false,
+    "The memory overview should not have a selection while recording.");
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has ended.");
 
   is(TimelineController.getMarkers().length, 0,
     "There are no markers available.");
-  is(TimelineView.overview.selectionEnabled, true,
-    "The selection should now be enabled for the overview graph.");
-  is(TimelineView.overview.hasSelection(), false,
-    "The overview graph should not have a selection after recording.");
+  isnot(TimelineController.getMemory().length, 0,
+    "There are some memory measurements available.");
+
+  is(TimelineView.markersOverview.selectionEnabled, true,
+    "The selection should now be enabled for the markers overview.");
+  is(TimelineView.markersOverview.hasSelection(), false,
+    "The markers overview should not have a selection after recording.");
+
+  is(TimelineView.memoryOverview.selectionEnabled, true,
+    "The selection should now be enabled for the memory overview.");
+  is(TimelineView.memoryOverview.hasSelection(), false,
+    "The memory overview should not have a selection after recording.");
 
   yield teardown(panel);
   finish();
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/timeline/test/browser_timeline_recording-without-memory.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the timeline actor isn't unnecessarily asked to record memory.
+ */
+
+let test = Task.async(function*() {
+  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
+  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
+
+  yield TimelineController.toggleRecording();
+  ok(true, "Recording has started.");
+
+  let updated = 0;
+  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
+
+  ok((yield waitUntil(() => updated > 10)),
+    "The overview graph was updated a bunch of times.");
+  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
+    "There are some markers available.");
+
+  yield TimelineController.toggleRecording();
+  ok(true, "Recording has ended.");
+
+  let markers = TimelineController.getMarkers();
+  let memory = TimelineController.getMemory();
+
+  isnot(markers.length, 0,
+    "There are some markers available.");
+  is(memory.length, 0,
+    "There are no memory measurements available.");
+
+  yield teardown(panel);
+  finish();
+});
--- a/browser/devtools/timeline/test/browser_timeline_recording.js
+++ b/browser/devtools/timeline/test/browser_timeline_recording.js
@@ -2,33 +2,39 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the timeline can properly start and stop a recording.
  */
 
 let test = Task.async(function*() {
   let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { gFront, TimelineController } = panel.panelWin;
+  let { $, gFront, TimelineController } = panel.panelWin;
+
+  $("#memory-checkbox").checked = true;
+  yield TimelineController.updateMemoryRecording();
 
   is((yield gFront.isRecording()), false,
     "The timeline actor should not be recording when the tool starts.");
   is(TimelineController.getMarkers().length, 0,
     "There should be no markers available when the tool starts.");
 
   yield TimelineController.toggleRecording();
 
   is((yield gFront.isRecording()), true,
     "The timeline actor should be recording now.");
   ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
     "There are some markers available now.");
+  ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
+    "There are some memory measurements available now.");
 
-  ok("startTime" in TimelineController.getMarkers(),
-    "A `startTime` field was set on the markers array.");
-  ok("endTime" in TimelineController.getMarkers(),
-    "An `endTime` field was set on the markers array.");
-  ok(TimelineController.getMarkers().endTime >
-     TimelineController.getMarkers().startTime,
+  ok("startTime" in TimelineController.getInterval(),
+    "A `startTime` field was set on the recording data.");
+  ok("endTime" in TimelineController.getInterval(),
+    "An `endTime` field was set on the recording data.");
+
+  ok(TimelineController.getInterval().endTime >
+     TimelineController.getInterval().startTime,
     "Some time has passed since the recording started.");
 
   yield teardown(panel);
   finish();
 });
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-background.js
+++ b/browser/devtools/timeline/test/browser_timeline_waterfall-background.js
@@ -12,17 +12,17 @@ let test = Task.async(function*() {
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has started.");
 
   let updated = 0;
   panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
 
   ok((yield waitUntil(() => updated > 0)),
-    "The overview graph was updated a bunch of times.");
+    "The overview graphs were updated a bunch of times.");
   ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
     "There are some markers available.");
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has ended.");
 
   // Test the waterfall background.
 
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
+++ b/browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
@@ -11,58 +11,58 @@ let test = Task.async(function*() {
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has started.");
 
   let updated = 0;
   panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
 
   ok((yield waitUntil(() => updated > 0)),
-    "The overview graph was updated a bunch of times.");
+    "The overview graphs were updated a bunch of times.");
   ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
     "There are some markers available.");
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has ended.");
 
   // Test the header container.
 
-  ok($(".timeline-header-container"),
+  ok($(".waterfall-header-container"),
     "A header container should have been created.");
 
   // Test the header sidebar (left).
 
-  ok($(".timeline-header-sidebar"),
+  ok($(".waterfall-header-container > .waterfall-sidebar"),
     "A header sidebar node should have been created.");
-  ok($(".timeline-header-sidebar > .timeline-header-name"),
+  ok($(".waterfall-header-container > .waterfall-sidebar > .waterfall-header-name"),
     "A header name label should have been created inside the sidebar.");
 
   // Test the header ticks (right).
 
-  ok($(".timeline-header-ticks"),
+  ok($(".waterfall-header-ticks"),
     "A header ticks node should have been created.");
-  ok($$(".timeline-header-ticks > .timeline-header-tick").length > 0,
+  ok($$(".waterfall-header-ticks > .waterfall-header-tick").length > 0,
     "Some header tick labels should have been created inside the tick node.");
 
   // Test the markers container.
 
-  ok($(".timeline-marker-container"),
+  ok($(".waterfall-marker-container"),
     "A marker container should have been created.");
 
   // Test the markers sidebar (left).
 
-  ok($$(".timeline-marker-sidebar").length,
+  ok($$(".waterfall-marker-container > .waterfall-sidebar").length,
     "Some marker sidebar nodes should have been created.");
-  ok($$(".timeline-marker-sidebar:not(spacer) > .timeline-marker-bullet").length,
+  ok($$(".waterfall-marker-container > .waterfall-sidebar:not(spacer) > .waterfall-marker-bullet").length,
     "Some marker color bullets should have been created inside the sidebar.");
-  ok($$(".timeline-marker-sidebar:not(spacer) > .timeline-marker-name").length,
+  ok($$(".waterfall-marker-container > .waterfall-sidebar:not(spacer) > .waterfall-marker-name").length,
     "Some marker name labels should have been created inside the sidebar.");
 
   // Test the markers waterfall (right).
 
-  ok($$(".timeline-marker-waterfall").length,
+  ok($$(".waterfall-marker-item").length,
     "Some marker waterfall nodes should have been created.");
-  ok($$(".timeline-marker-waterfall:not(spacer) > .timeline-marker-bar").length,
+  ok($$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar").length,
     "Some marker color bars should have been created inside the waterfall.");
 
   yield teardown(panel);
   finish();
 });
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
+++ b/browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
@@ -22,17 +22,17 @@ let test = Task.async(function*() {
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has started.");
 
   let updated = 0;
   panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
 
   ok((yield waitUntil(() => updated > 0)),
-    "The overview graph was updated a bunch of times.");
+    "The overview graphs were updated a bunch of times.");
   ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
     "There are some markers available.");
 
   yield TimelineController.toggleRecording();
   ok(true, "Recording has ended.");
 
   // Test the table sidebars.
 
--- a/browser/devtools/timeline/timeline.js
+++ b/browser/devtools/timeline/timeline.js
@@ -7,34 +7,39 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 
 devtools.lazyRequireGetter(this, "promise");
 devtools.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
-devtools.lazyRequireGetter(this, "Overview",
-  "devtools/timeline/overview", true);
+devtools.lazyRequireGetter(this, "MarkersOverview",
+  "devtools/timeline/markers-overview", true);
+devtools.lazyRequireGetter(this, "MemoryOverview",
+  "devtools/timeline/memory-overview", true);
 devtools.lazyRequireGetter(this, "Waterfall",
   "devtools/timeline/waterfall", true);
 
+devtools.lazyImporter(this, "CanvasGraphUtils",
+  "resource:///modules/devtools/Graphs.jsm");
+
 devtools.lazyImporter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 
 const OVERVIEW_UPDATE_INTERVAL = 200;
 const OVERVIEW_INITIAL_SELECTION_RATIO = 0.15;
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // When a recording is started or stopped, via the `stopwatch` button.
   RECORDING_STARTED: "Timeline:RecordingStarted",
   RECORDING_ENDED: "Timeline:RecordingEnded",
 
-  // When the overview graph is populated with new markers.
+  // When the overview graphs are populated with new markers.
   OVERVIEW_UPDATED: "Timeline:OverviewUpdated",
 
   // When the waterfall view is populated with new markers.
   WATERFALL_UPDATED: "Timeline:WaterfallUpdated"
 };
 
 /**
  * The current target and the timeline front, set by this tool's host.
@@ -58,225 +63,330 @@ let shutdownTimeline = Task.async(functi
   yield gFront.stop();
 });
 
 /**
  * Functions handling the timeline frontend controller.
  */
 let TimelineController = {
   /**
-   * Permanent storage for the markers streamed by the backend.
+   * Permanent storage for the markers and the memory measurements streamed by
+   * the backend, along with the start and end timestamps.
    */
+  _starTime: 0,
+  _endTime: 0,
   _markers: [],
+  _memory: [],
 
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: function() {
     this._onRecordingTick = this._onRecordingTick.bind(this);
     this._onMarkers = this._onMarkers.bind(this);
+    this._onMemory = this._onMemory.bind(this);
     gFront.on("markers", this._onMarkers);
+    gFront.on("memory", this._onMemory);
   },
 
   /**
    * Destruction function, called when the tool is closed.
    */
   destroy: function() {
     gFront.off("markers", this._onMarkers);
+    gFront.off("memory", this._onMemory);
+  },
+
+  /**
+   * Gets the { stat, end } time interval for this recording.
+   * @return object
+   */
+  getInterval: function() {
+    return { startTime: this._startTime, endTime: this._endTime };
   },
 
   /**
    * Gets the accumulated markers in this recording.
-   * @return array.
+   * @return array
    */
   getMarkers: function() {
     return this._markers;
   },
 
   /**
+   * Gets the accumulated memory measurements in this recording.
+   * @return array
+   */
+  getMemory: function() {
+    return this._memory;
+  },
+
+  /**
+   * Updates the views to show or hide the memory recording data.
+   */
+  updateMemoryRecording: Task.async(function*() {
+    if ($("#memory-checkbox").checked) {
+      yield TimelineView.showMemoryOverview();
+    } else {
+      yield TimelineView.hideMemoryOverview();
+    }
+  }),
+
+  /**
    * Starts/stops the timeline recording and streaming.
    */
   toggleRecording: Task.async(function*() {
     let isRecording = yield gFront.isRecording();
     if (isRecording == false) {
       yield this._startRecording();
     } else {
       yield this._stopRecording();
     }
   }),
 
   /**
    * Starts the recording, updating the UI as needed.
    */
   _startRecording: function*() {
     TimelineView.handleRecordingStarted();
-    let startTime = yield gFront.start();
+
+    let withMemory = $("#memory-checkbox").checked;
+    let startTime = yield gFront.start({ withMemory });
+
     // Times must come from the actor in order to be self-consistent.
     // However, we also want to update the view with the elapsed time
     // even when the actor is not generating data.  To do this we get
     // the local time and use it to compute a reasonable elapsed time.
     // See _onRecordingTick.
     this._localStartTime = performance.now();
-
+    this._startTime = startTime;
+    this._endTime = startTime;
     this._markers = [];
-    this._markers.startTime = startTime;
-    this._markers.endTime = startTime;
+    this._memory = [];
     this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
   },
 
   /**
    * Stops the recording, updating the UI as needed.
    */
   _stopRecording: function*() {
     clearInterval(this._updateId);
 
     // Sorting markers is only important when displayed in the waterfall.
     this._markers = this._markers.sort((a,b) => (a.start > b.start));
 
-    TimelineView.handleMarkersUpdate(this._markers);
+    TimelineView.handleRecordingUpdate();
     TimelineView.handleRecordingEnded();
     yield gFront.stop();
   },
 
   /**
    * Used in tests. Stops the recording, discarding the accumulated markers and
    * updating the UI as needed.
    */
   _stopRecordingAndDiscardData: function*() {
     yield this._stopRecording();
     this._markers.length = 0;
+    this._memory.length = 0;
   },
 
   /**
    * Callback handling the "markers" event on the timeline front.
    *
    * @param array markers
    *        A list of new markers collected since the last time this
    *        function was invoked.
    * @param number endTime
    *        A time after the last marker in markers was collected.
    */
   _onMarkers: function(markers, endTime) {
     Array.prototype.push.apply(this._markers, markers);
-    this._markers.endTime = endTime;
+    this._endTime = endTime;
+  },
+
+  /**
+   * Callback handling the "memory" event on the timeline front.
+   *
+   * @param number delta
+   *        The number of milliseconds elapsed since epoch.
+   * @param object measurement
+   *        A detailed breakdown of the current memory usage.
+   */
+  _onMemory: function(delta, measurement) {
+    this._memory.push({ delta, value: measurement.total / 1024 / 1024 });
   },
 
   /**
    * Callback invoked at a fixed interval while recording.
-   * Updates the markers store with the current time and the timeline overview.
+   * Updates the current time and the timeline overview.
    */
   _onRecordingTick: function() {
     // Compute an approximate ending time for the view.  This is
     // needed to ensure that the view updates even when new data is
     // not being generated.
-    let fakeTime = this._markers.startTime + (performance.now() - this._localStartTime);
-    if (fakeTime > this._markers.endTime) {
-      this._markers.endTime = fakeTime;
+    let fakeTime = this._startTime + (performance.now() - this._localStartTime);
+    if (fakeTime > this._endTime) {
+      this._endTime = fakeTime;
     }
-    TimelineView.handleMarkersUpdate(this._markers);
+    TimelineView.handleRecordingUpdate();
   }
 };
 
 /**
  * Functions handling the timeline frontend view.
  */
 let TimelineView = {
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: Task.async(function*() {
-    this.overview = new Overview($("#timeline-overview"));
+    this.markersOverview = new MarkersOverview($("#markers-overview"));
     this.waterfall = new Waterfall($("#timeline-waterfall"));
 
     this._onSelecting = this._onSelecting.bind(this);
     this._onRefresh = this._onRefresh.bind(this);
-    this.overview.on("selecting", this._onSelecting);
-    this.overview.on("refresh", this._onRefresh);
+    this.markersOverview.on("selecting", this._onSelecting);
+    this.markersOverview.on("refresh", this._onRefresh);
 
-    yield this.overview.ready();
+    yield this.markersOverview.ready();
     yield this.waterfall.recalculateBounds();
   }),
 
   /**
    * Destruction function, called when the tool is closed.
    */
   destroy: function() {
-    this.overview.off("selecting", this._onSelecting);
-    this.overview.off("refresh", this._onRefresh);
-    this.overview.destroy();
+    this.markersOverview.off("selecting", this._onSelecting);
+    this.markersOverview.off("refresh", this._onRefresh);
+    this.markersOverview.destroy();
+
+    // The memory overview graph is not always available.
+    if (this.memoryOverview) {
+      this.memoryOverview.destroy();
+    }
+  },
+
+  /**
+   * Shows the memory overview graph.
+   */
+  showMemoryOverview: Task.async(function*() {
+    this.memoryOverview = new MemoryOverview($("#memory-overview"));
+    yield this.memoryOverview.ready();
+
+    let interval = TimelineController.getInterval();
+    let memory = TimelineController.getMemory();
+    this.memoryOverview.setData({ interval, memory });
+
+    CanvasGraphUtils.linkAnimation(this.markersOverview, this.memoryOverview);
+    CanvasGraphUtils.linkSelection(this.markersOverview, this.memoryOverview);
+  }),
+
+  /**
+   * Hides the memory overview graph.
+   */
+  hideMemoryOverview: function() {
+    if (!this.memoryOverview) {
+      return;
+    }
+    this.memoryOverview.destroy();
+    this.memoryOverview = null;
   },
 
   /**
    * Signals that a recording session has started and triggers the appropriate
    * changes in the UI.
    */
   handleRecordingStarted: function() {
     $("#record-button").setAttribute("checked", "true");
+    $("#memory-checkbox").setAttribute("disabled", "true");
     $("#timeline-pane").selectedPanel = $("#recording-notice");
 
-    this.overview.selectionEnabled = false;
-    this.overview.dropSelection();
-    this.overview.setData([]);
+    this.markersOverview.clearView();
+
+    // The memory overview graph is not always available.
+    if (this.memoryOverview) {
+      this.memoryOverview.clearView();
+    }
+
     this.waterfall.clearView();
 
     window.emit(EVENTS.RECORDING_STARTED);
   },
 
   /**
    * Signals that a recording session has ended and triggers the appropriate
    * changes in the UI.
    */
   handleRecordingEnded: function() {
     $("#record-button").removeAttribute("checked");
+    $("#memory-checkbox").removeAttribute("disabled");
     $("#timeline-pane").selectedPanel = $("#timeline-waterfall");
 
-    this.overview.selectionEnabled = true;
+    this.markersOverview.selectionEnabled = true;
 
+    // The memory overview graph is not always available.
+    if (this.memoryOverview) {
+      this.memoryOverview.selectionEnabled = true;
+    }
+
+    let interval = TimelineController.getInterval();
     let markers = TimelineController.getMarkers();
+    let memory = TimelineController.getMemory();
+
     if (markers.length) {
-      let start = (markers[0].start - markers.startTime) * this.overview.dataScaleX;
-      let end = start + this.overview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
-      this.overview.setSelection({ start, end });
+      let start = (markers[0].start - interval.startTime) * this.markersOverview.dataScaleX;
+      let end = start + this.markersOverview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
+      this.markersOverview.setSelection({ start, end });
     } else {
-      let duration = markers.endTime - markers.startTime;
-      this.waterfall.setData(markers, markers.startTime, markers.endTime);
+      let timeStart = interval.startTime;
+      let timeEnd = interval.endTime;
+      this.waterfall.setData(markers, timeStart, timeStart, timeEnd);
     }
 
     window.emit(EVENTS.RECORDING_ENDED);
   },
 
   /**
    * Signals that a new set of markers was made available by the controller,
    * or that the overview graph needs to be updated.
-   *
-   * @param array markers
-   *        A list of new markers collected since the recording has started.
    */
-  handleMarkersUpdate: function(markers) {
-    this.overview.setData(markers);
+  handleRecordingUpdate: function() {
+    let interval = TimelineController.getInterval();
+    let markers = TimelineController.getMarkers();
+    let memory = TimelineController.getMemory();
+
+    this.markersOverview.setData({ interval, markers });
+
+    // The memory overview graph is not always available.
+    if (this.memoryOverview) {
+      this.memoryOverview.setData({ interval, memory });
+    }
+
     window.emit(EVENTS.OVERVIEW_UPDATED);
   },
 
   /**
    * Callback handling the "selecting" event on the timeline overview.
    */
   _onSelecting: function() {
-    if (!this.overview.hasSelection() &&
-        !this.overview.hasSelectionInProgress()) {
+    if (!this.markersOverview.hasSelection() &&
+        !this.markersOverview.hasSelectionInProgress()) {
       this.waterfall.clearView();
       return;
     }
-    let selection = this.overview.getSelection();
-    let start = selection.start / this.overview.dataScaleX;
-    let end = selection.end / this.overview.dataScaleX;
+    let selection = this.markersOverview.getSelection();
+    let start = selection.start / this.markersOverview.dataScaleX;
+    let end = selection.end / this.markersOverview.dataScaleX;
 
     let markers = TimelineController.getMarkers();
-    let timeStart = markers.startTime + Math.min(start, end);
-    let timeEnd = markers.startTime + Math.max(start, end);
-    this.waterfall.setData(markers, timeStart, timeEnd);
+    let interval = TimelineController.getInterval();
+
+    let timeStart = interval.startTime + Math.min(start, end);
+    let timeEnd = interval.startTime + Math.max(start, end);
+    this.waterfall.setData(markers, interval.startTime, timeStart, timeEnd);
   },
 
   /**
    * Callback handling the "refresh" event on the timeline overview.
    */
   _onRefresh: function() {
     this.waterfall.recalculateBounds();
     this._onSelecting();
--- a/browser/devtools/timeline/timeline.xul
+++ b/browser/devtools/timeline/timeline.xul
@@ -20,23 +20,27 @@
              class="devtools-toolbar">
       <hbox id="recordings-controls"
             class="devtools-toolbarbutton-group"
             align="center">
         <toolbarbutton id="record-button"
                        class="devtools-toolbarbutton"
                        oncommand="TimelineController.toggleRecording()"
                        tooltiptext="&timelineUI.recordButton.tooltip;"/>
-        <spacer flex="1"/>
+        <checkbox id="memory-checkbox"
+                  label="&timelineUI.memoryCheckbox.label;"
+                  oncommand="TimelineController.updateMemoryRecording()"
+                  tooltiptext="&timelineUI.memoryCheckbox.tooltip;"/>
         <label id="record-label"
                value="&timelineUI.recordLabel;"/>
       </hbox>
     </toolbar>
 
-    <vbox id="timeline-overview"/>
+    <vbox id="markers-overview"/>
+    <vbox id="memory-overview"/>
 
     <deck id="timeline-pane"
           flex="1">
       <hbox id="empty-notice"
             class="notice-container"
             align="center"
             pack="center"
             flex="1">
rename from browser/devtools/timeline/widgets/overview.js
rename to browser/devtools/timeline/widgets/markers-overview.js
--- a/browser/devtools/timeline/widgets/overview.js
+++ b/browser/devtools/timeline/widgets/markers-overview.js
@@ -1,73 +1,73 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 /**
- * This file contains the "overview" graph, which is a minimap of all the
- * timeline data. Regions inside it may be selected, determining which markers
- * are visible in the "waterfall".
+ * This file contains the "markers overview" graph, which is a minimap of all
+ * the timeline data. Regions inside it may be selected, determining which
+ * markers are visible in the "waterfall".
  */
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 
 Cu.import("resource:///modules/devtools/Graphs.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 loader.lazyRequireGetter(this, "L10N",
   "devtools/timeline/global", true);
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/timeline/global", true);
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
-const OVERVIEW_HEADER_HEIGHT = 20; // px
-const OVERVIEW_BODY_HEIGHT = 50; // px
+const OVERVIEW_HEADER_HEIGHT = 14; // px
+const OVERVIEW_BODY_HEIGHT = 55; // 11px * 5 groups
 
 const OVERVIEW_BACKGROUND_COLOR = "#fff";
 const OVERVIEW_CLIPHEAD_LINE_COLOR = "#666";
 const OVERVIEW_SELECTION_LINE_COLOR = "#555";
 const OVERVIEW_SELECTION_BACKGROUND_COLOR = "rgba(76,158,217,0.25)";
 const OVERVIEW_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
 
 const OVERVIEW_HEADER_TICKS_MULTIPLE = 100; // ms
 const OVERVIEW_HEADER_TICKS_SPACING_MIN = 75; // px
 const OVERVIEW_HEADER_SAFE_BOUNDS = 50; // px
-const OVERVIEW_HEADER_BACKGROUND = "#ebeced";
+const OVERVIEW_HEADER_BACKGROUND = "#fff";
 const OVERVIEW_HEADER_TEXT_COLOR = "#18191a";
 const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px
 const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif";
-const OVERVIEW_HEADER_TEXT_PADDING = 6; // px
-const OVERVIEW_TIMELINE_STROKES = "#aaa";
+const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px
+const OVERVIEW_HEADER_TEXT_PADDING_TOP = 1; // px
+const OVERVIEW_TIMELINE_STROKES = "#ccc";
 const OVERVIEW_MARKERS_COLOR_STOPS = [0, 0.1, 0.75, 1];
 const OVERVIEW_MARKER_DURATION_MIN = 4; // ms
-const OVERVIEW_GROUP_VERTICAL_PADDING = 6; // px
+const OVERVIEW_GROUP_VERTICAL_PADDING = 5; // px
 const OVERVIEW_GROUP_ALTERNATING_BACKGROUND = "rgba(0,0,0,0.05)";
 
 /**
- * An overview for the timeline data.
+ * An overview for the markers data.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the overview.
  */
-function Overview(parent, ...args) {
-  AbstractCanvasGraph.apply(this, [parent, "timeline-overview", ...args]);
+function MarkersOverview(parent, ...args) {
+  AbstractCanvasGraph.apply(this, [parent, "markers-overview", ...args]);
   this.once("ready", () => {
+    // Set the list of names, properties and colors used to paint this overview.
     this.setBlueprint(TIMELINE_BLUEPRINT);
 
-    var preview = [];
-    preview.startTime = 0;
-    preview.endTime = 1000;
-    this.setData(preview);
+    // Populate this overview with some dummy initial data.
+    this.setData({ interval: { startTime: 0, endTime: 1000 }, markers: [] });
   });
 }
 
-Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
+MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
   fixedHeight: OVERVIEW_HEADER_HEIGHT + OVERVIEW_BODY_HEIGHT,
   clipheadLineColor: OVERVIEW_CLIPHEAD_LINE_COLOR,
   selectionLineColor: OVERVIEW_SELECTION_LINE_COLOR,
   selectionBackgroundColor: OVERVIEW_SELECTION_BACKGROUND_COLOR,
   selectionStripesColor: OVERVIEW_SELECTION_STRIPES_COLOR,
 
   /**
    * List of names and colors used to paint this overview.
@@ -79,83 +79,97 @@ Overview.prototype = Heritage.extend(Abs
 
     for (let type in blueprint) {
       this._paintBatches.set(type, { style: blueprint[type], batch: [] });
       this._lastGroup = Math.max(this._lastGroup, blueprint[type].group);
     }
   },
 
   /**
+   * Disables selection and empties this graph.
+   */
+  clearView: function() {
+    this.selectionEnabled = false;
+    this.dropSelection();
+    this.setData({ interval: { startTime: 0, endTime: 0 }, markers: [] });
+  },
+
+  /**
    * Renders the graph's data source.
    * @see AbstractCanvasGraph.prototype.buildGraphImage
    */
   buildGraphImage: function() {
+    let { interval, markers } = this._data;
+    let { startTime, endTime } = interval;
+
     let { canvas, ctx } = this._getNamedCanvas("overview-data");
     let canvasWidth = this._width;
     let canvasHeight = this._height;
     let safeBounds = OVERVIEW_HEADER_SAFE_BOUNDS * this._pixelRatio;
     let availableWidth = canvasWidth - safeBounds;
 
     // Group markers into separate paint batches. This is necessary to
     // draw all markers sharing the same style at once.
 
-    for (let marker of this._data) {
+    for (let marker of markers) {
       this._paintBatches.get(marker.name).batch.push(marker);
     }
 
     // Calculate each group's height, and the time-based scaling.
 
     let totalGroups = this._lastGroup + 1;
     let headerHeight = OVERVIEW_HEADER_HEIGHT * this._pixelRatio;
     let groupHeight = OVERVIEW_BODY_HEIGHT * this._pixelRatio / totalGroups;
     let groupPadding = OVERVIEW_GROUP_VERTICAL_PADDING * this._pixelRatio;
 
-    let totalTime = (this._data.endTime - this._data.startTime) || 0;
+    let totalTime = (endTime - startTime) || 0;
     let dataScale = this.dataScaleX = availableWidth / totalTime;
 
     // Draw the header and overview background.
 
     ctx.fillStyle = OVERVIEW_HEADER_BACKGROUND;
     ctx.fillRect(0, 0, canvasWidth, headerHeight);
 
     ctx.fillStyle = OVERVIEW_BACKGROUND_COLOR;
     ctx.fillRect(0, headerHeight, canvasWidth, canvasHeight);
 
     // Draw the alternating odd/even group backgrounds.
 
     ctx.fillStyle = OVERVIEW_GROUP_ALTERNATING_BACKGROUND;
     ctx.beginPath();
 
-    for (let i = 1; i < totalGroups; i += 2) {
+    for (let i = 0; i < totalGroups; i += 2) {
       let top = headerHeight + i * groupHeight;
       ctx.rect(0, top, canvasWidth, groupHeight);
     }
 
     ctx.fill();
 
     // Draw the timeline header ticks.
 
-    ctx.textBaseline = "middle";
     let fontSize = OVERVIEW_HEADER_TEXT_FONT_SIZE * this._pixelRatio;
     let fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY;
+    let textPaddingLeft = OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio;
+    let textPaddingTop = OVERVIEW_HEADER_TEXT_PADDING_TOP * this._pixelRatio;
+    let tickInterval = this._findOptimalTickInterval(dataScale);
+
+    ctx.textBaseline = "middle";
     ctx.font = fontSize + "px " + fontFamily;
     ctx.fillStyle = OVERVIEW_HEADER_TEXT_COLOR;
     ctx.strokeStyle = OVERVIEW_TIMELINE_STROKES;
     ctx.beginPath();
 
-    let tickInterval = this._findOptimalTickInterval(dataScale);
-    let headerTextPadding = OVERVIEW_HEADER_TEXT_PADDING * this._pixelRatio;
-
     for (let x = 0; x < availableWidth; x += tickInterval) {
-      let left = x + headerTextPadding;
+      let lineLeft = x;
+      let textLeft = lineLeft + textPaddingLeft;
       let time = Math.round(x / dataScale);
       let label = L10N.getFormatStr("timeline.tick", time);
-      ctx.fillText(label, left, headerHeight / 2 + 1);
-      ctx.moveTo(x, 0);
-      ctx.lineTo(x, canvasHeight);
+      ctx.fillText(label, textLeft, headerHeight / 2 + textPaddingTop);
+      ctx.moveTo(lineLeft, 0);
+      ctx.lineTo(lineLeft, canvasHeight);
     }
 
     ctx.stroke();
 
     // Draw the timeline markers.
 
     for (let [, { style, batch }] of this._paintBatches) {
       let top = headerHeight + style.group * groupHeight + groupPadding / 2;
@@ -165,18 +179,18 @@ Overview.prototype = Heritage.extend(Abs
       gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[0], style.stroke);
       gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[1], style.fill);
       gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[2], style.fill);
       gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[3], style.stroke);
       ctx.fillStyle = gradient;
       ctx.beginPath();
 
       for (let { start, end } of batch) {
-        start -= this._data.startTime;
-        end -= this._data.startTime;
+        start -= interval.startTime;
+        end -= interval.startTime;
 
         let left = start * dataScale;
         let duration = Math.max(end - start, OVERVIEW_MARKER_DURATION_MIN);
         let width = Math.max(duration * dataScale, this._pixelRatio);
         ctx.rect(left, top, width, height);
       }
 
       ctx.fill();
@@ -203,9 +217,9 @@ Overview.prototype = Heritage.extend(Abs
         timingStep <<= 1;
         continue;
       }
       return scaledStep;
     }
   }
 });
 
-exports.Overview = Overview;
+exports.MarkersOverview = MarkersOverview;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/timeline/widgets/memory-overview.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * This file contains the "memory overview" graph, a simple representation of
+ * of all the memory measurements taken while streaming the timeline data.
+ */
+
+const {Cc, Ci, Cu, Cr} = require("chrome");
+
+Cu.import("resource:///modules/devtools/Graphs.jsm");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+
+loader.lazyRequireGetter(this, "L10N",
+  "devtools/timeline/global", true);
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+const OVERVIEW_HEIGHT = 30; // px
+
+const OVERVIEW_BACKGROUND_COLOR = "#fff";
+const OVERVIEW_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.1)";
+const OVERVIEW_BACKGROUND_GRADIENT_END = "rgba(0,136,204,0.0)";
+const OVERVIEW_STROKE_WIDTH = 1; // px
+const OVERVIEW_STROKE_COLOR = "rgba(0,136,204,1)";
+const OVERVIEW_CLIPHEAD_LINE_COLOR = "#666";
+const OVERVIEW_SELECTION_LINE_COLOR = "#555";
+const OVERVIEW_MAXIMUM_LINE_COLOR = "rgba(0,136,204,0.4)";
+const OVERVIEW_AVERAGE_LINE_COLOR = "rgba(0,136,204,0.7)";
+const OVERVIEW_MINIMUM_LINE_COLOR = "rgba(0,136,204,0.9)";
+
+const OVERVIEW_SELECTION_BACKGROUND_COLOR = "rgba(76,158,217,0.25)";
+const OVERVIEW_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
+
+/**
+ * An overview for the memory data.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the overview.
+ */
+function MemoryOverview(parent) {
+  LineGraphWidget.call(this, parent, L10N.getStr("graphs.memory"));
+
+  this.once("ready", () => {
+    // Populate this overview with some dummy initial data.
+    this.setData({ interval: { startTime: 0, endTime: 1000 }, memory: [] });
+  });
+}
+
+MemoryOverview.prototype = Heritage.extend(LineGraphWidget.prototype, {
+  dampenValuesFactor: 0.95,
+  fixedHeight: OVERVIEW_HEIGHT,
+  backgroundColor: OVERVIEW_BACKGROUND_COLOR,
+  backgroundGradientStart: OVERVIEW_BACKGROUND_GRADIENT_START,
+  backgroundGradientEnd: OVERVIEW_BACKGROUND_GRADIENT_END,
+  strokeColor: OVERVIEW_STROKE_COLOR,
+  strokeWidth: OVERVIEW_STROKE_WIDTH,
+  maximumLineColor: OVERVIEW_MAXIMUM_LINE_COLOR,
+  averageLineColor: OVERVIEW_AVERAGE_LINE_COLOR,
+  minimumLineColor: OVERVIEW_MINIMUM_LINE_COLOR,
+  clipheadLineColor: OVERVIEW_CLIPHEAD_LINE_COLOR,
+  selectionLineColor: OVERVIEW_SELECTION_LINE_COLOR,
+  selectionBackgroundColor: OVERVIEW_SELECTION_BACKGROUND_COLOR,
+  selectionStripesColor: OVERVIEW_SELECTION_STRIPES_COLOR,
+  withTooltipArrows: false,
+  withFixedTooltipPositions: true,
+
+  /**
+   * Disables selection and empties this graph.
+   */
+  clearView: function() {
+    this.selectionEnabled = false;
+    this.dropSelection();
+    this.setData({ interval: { startTime: 0, endTime: 0 }, memory: [] });
+  },
+
+  /**
+   * Sets the data source for this graph.
+   */
+  setData: function({ interval, memory }) {
+    this.dataOffsetX = interval.startTime;
+    LineGraphWidget.prototype.setData.call(this, memory);
+  }
+});
+
+exports.MemoryOverview = MemoryOverview;
--- a/browser/devtools/timeline/widgets/waterfall.js
+++ b/browser/devtools/timeline/widgets/waterfall.js
@@ -17,51 +17,51 @@ loader.lazyRequireGetter(this, "TIMELINE
 
 loader.lazyImporter(this, "setNamedTimeout",
   "resource:///modules/devtools/ViewHelpers.jsm");
 loader.lazyImporter(this, "clearNamedTimeout",
   "resource:///modules/devtools/ViewHelpers.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
-const TIMELINE_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
-const TIMELINE_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
+const WATERFALL_SIDEBAR_WIDTH = 150; // px
 
-const TIMELINE_HEADER_TICKS_MULTIPLE = 5; // ms
-const TIMELINE_HEADER_TICKS_SPACING_MIN = 50; // px
-const TIMELINE_HEADER_TEXT_PADDING = 3; // px
+const WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
+const WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
 
-const TIMELINE_MARKER_SIDEBAR_WIDTH = 150; // px
-const TIMELINE_MARKER_BAR_WIDTH_MIN = 5; // px
+const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
+const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
+const WATERFALL_HEADER_TEXT_PADDING = 3; // px
 
 const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
 const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
 const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
 const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
 const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
 const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
+const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
 
 /**
  * A detailed waterfall view for the timeline data.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the waterfall.
  */
 function Waterfall(parent) {
   this._parent = parent;
   this._document = parent.ownerDocument;
   this._fragment = this._document.createDocumentFragment();
   this._outstandingMarkers = [];
 
   this._headerContents = this._document.createElement("hbox");
-  this._headerContents.className = "timeline-header-contents";
+  this._headerContents.className = "waterfall-header-contents";
   this._parent.appendChild(this._headerContents);
 
   this._listContents = this._document.createElement("vbox");
-  this._listContents.className = "timeline-list-contents";
+  this._listContents.className = "waterfall-list-contents";
   this._listContents.setAttribute("flex", "1");
   this._parent.appendChild(this._listContents);
 
   this._isRTL = this._getRTL();
 
   // Lazy require is a bit slow, and these are hot objects.
   this._l10n = L10N;
   this._blueprint = TIMELINE_BLUEPRINT;
@@ -70,28 +70,31 @@ function Waterfall(parent) {
 }
 
 Waterfall.prototype = {
   /**
    * Populates this view with the provided data source.
    *
    * @param array markers
    *        A list of markers received from the controller.
+   * @param number timeEpoch
+   *        The absolute time (in milliseconds) when the recording started.
    * @param number timeStart
    *        The time (in milliseconds) to start drawing from.
    * @param number timeEnd
    *        The time (in milliseconds) to end drawing at.
    */
-  setData: function(markers, timeStart, timeEnd) {
+  setData: function(markers, timeEpoch, timeStart, timeEnd) {
     this.clearView();
 
     let dataScale = this._waterfallWidth / (timeEnd - timeStart);
     this._drawWaterfallBackground(dataScale);
+
     // Label the header as if the first possible marker was at T=0.
-    this._buildHeader(this._headerContents, timeStart - markers.startTime, dataScale);
+    this._buildHeader(this._headerContents, timeStart - timeEpoch, dataScale);
     this._buildMarkers(this._listContents, markers, timeStart, timeEnd, dataScale);
   },
 
   /**
    * Depopulates this view.
    */
   clearView: function() {
     while (this._headerContents.hasChildNodes()) {
@@ -106,66 +109,66 @@ Waterfall.prototype = {
   },
 
   /**
    * Calculates and stores the available width for the waterfall.
    * This should be invoked every time the container window is resized.
    */
   recalculateBounds: function() {
     let bounds = this._parent.getBoundingClientRect();
-    this._waterfallWidth = bounds.width - TIMELINE_MARKER_SIDEBAR_WIDTH;
+    this._waterfallWidth = bounds.width - WATERFALL_SIDEBAR_WIDTH;
   },
 
   /**
    * Creates the header part of this view.
    *
    * @param nsIDOMNode parent
    *        The parent node holding the header.
    * @param number timeStart
    *        @see Waterfall.prototype.setData
    * @param number dataScale
    *        The time scale of the data source.
    */
   _buildHeader: function(parent, timeStart, dataScale) {
     let container = this._document.createElement("hbox");
-    container.className = "timeline-header-container";
+    container.className = "waterfall-header-container";
     container.setAttribute("flex", "1");
 
     let sidebar = this._document.createElement("hbox");
-    sidebar.className = "timeline-header-sidebar theme-sidebar";
-    sidebar.setAttribute("width", TIMELINE_MARKER_SIDEBAR_WIDTH);
+    sidebar.className = "waterfall-sidebar theme-sidebar";
+    sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
     sidebar.setAttribute("align", "center");
     container.appendChild(sidebar);
 
     let name = this._document.createElement("label");
-    name.className = "plain timeline-header-name";
+    name.className = "plain waterfall-header-name";
     name.setAttribute("value", this._l10n.getStr("timeline.records"));
     sidebar.appendChild(name);
 
     let ticks = this._document.createElement("hbox");
-    ticks.className = "timeline-header-ticks";
+    ticks.className = "waterfall-header-ticks waterfall-background-ticks";
     ticks.setAttribute("align", "center");
     ticks.setAttribute("flex", "1");
     container.appendChild(ticks);
 
     let offset = this._isRTL ? this._waterfallWidth : 0;
     let direction = this._isRTL ? -1 : 1;
     let tickInterval = this._findOptimalTickInterval({
-      ticksMultiple: TIMELINE_HEADER_TICKS_MULTIPLE,
-      ticksSpacingMin: TIMELINE_HEADER_TICKS_SPACING_MIN,
+      ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
+      ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
       dataScale: dataScale
     });
 
     for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
-      let start = x + direction * TIMELINE_HEADER_TEXT_PADDING;
+      let start = x + direction * WATERFALL_HEADER_TEXT_PADDING;
       let time = Math.round(timeStart + x / dataScale);
       let label = this._l10n.getFormatStr("timeline.tick", time);
 
       let node = this._document.createElement("label");
-      node.className = "plain timeline-header-tick";
+      node.className = "plain waterfall-header-tick";
       node.style.transform = "translateX(" + (start - offset) + "px)";
       node.setAttribute("value", label);
       ticks.appendChild(node);
     }
 
     parent.appendChild(container);
   },
 
@@ -185,32 +188,32 @@ Waterfall.prototype = {
     for (let marker of markers) {
       if (!isMarkerInRange(marker, timeStart, timeEnd)) {
         continue;
       }
       // Only build and display a finite number of markers initially, to
       // preserve a snappy UI. After a certain delay, continue building the
       // outstanding markers while there's (hopefully) no user interaction.
       let arguments_ = [this._fragment, marker, timeStart, dataScale];
-      if (processed++ < TIMELINE_IMMEDIATE_DRAW_MARKERS_COUNT) {
+      if (processed++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
         this._buildMarker.apply(this, arguments_);
       } else {
         this._outstandingMarkers.push(arguments_);
       }
     }
 
     // If there are no outstanding markers, add a dummy "spacer" at the end
     // to fill up any remaining available space in the UI.
     if (!this._outstandingMarkers.length) {
       this._buildMarker(this._fragment, null);
     }
     // Otherwise prepare flushing the outstanding markers after a small delay.
     else {
       this._setNamedTimeout("flush-outstanding-markers",
-        TIMELINE_FLUSH_OUTSTANDING_MARKERS_DELAY,
+        WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY,
         () => this._buildOutstandingMarkers(parent));
     }
 
     parent.appendChild(this._fragment);
   },
 
   /**
    * Finishes building the outstanding markers in this view.
@@ -236,17 +239,17 @@ Waterfall.prototype = {
    *        The { name, start, end } marker in the data source.
    * @param timeStart
    *        @see Waterfall.prototype.setData
    * @param number dataScale
    *        @see Waterfall.prototype._buildMarkers
    */
   _buildMarker: function(parent, marker, timeStart, dataScale) {
     let container = this._document.createElement("hbox");
-    container.className = "timeline-marker-container";
+    container.className = "waterfall-marker-container";
 
     if (marker) {
       this._buildMarkerSidebar(container, marker);
       this._buildMarkerWaterfall(container, marker, timeStart, dataScale);
     } else {
       this._buildMarkerSpacer(container);
       container.setAttribute("flex", "1");
       container.setAttribute("is-spacer", "");
@@ -262,31 +265,31 @@ Waterfall.prototype = {
    *        The container node representing the marker in this view.
    * @param object marker
    *        @see Waterfall.prototype._buildMarker
    */
   _buildMarkerSidebar: function(container, marker) {
     let blueprint = this._blueprint[marker.name];
 
     let sidebar = this._document.createElement("hbox");
-    sidebar.className = "timeline-marker-sidebar theme-sidebar";
-    sidebar.setAttribute("width", TIMELINE_MARKER_SIDEBAR_WIDTH);
+    sidebar.className = "waterfall-sidebar theme-sidebar";
+    sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
     sidebar.setAttribute("align", "center");
 
     let bullet = this._document.createElement("hbox");
-    bullet.className = "timeline-marker-bullet";
+    bullet.className = "waterfall-marker-bullet";
     bullet.style.backgroundColor = blueprint.fill;
     bullet.style.borderColor = blueprint.stroke;
     bullet.setAttribute("type", marker.name);
     sidebar.appendChild(bullet);
 
     let name = this._document.createElement("label");
     name.setAttribute("crop", "end");
     name.setAttribute("flex", "1");
-    name.className = "plain timeline-marker-name";
+    name.className = "plain waterfall-marker-name";
 
     let label;
     if (marker.detail && marker.detail.causeName) {
       label = this._l10n.getFormatStr("timeline.markerDetailFormat",
                                       blueprint.label,
                                       marker.detail.causeName);
     } else {
       label = blueprint.label;
@@ -309,48 +312,49 @@ Waterfall.prototype = {
    *        @see Waterfall.prototype.setData
    * @param number dataScale
    *        @see Waterfall.prototype._buildMarkers
    */
   _buildMarkerWaterfall: function(container, marker, timeStart, dataScale) {
     let blueprint = this._blueprint[marker.name];
 
     let waterfall = this._document.createElement("hbox");
-    waterfall.className = "timeline-marker-waterfall";
+    waterfall.className = "waterfall-marker-item waterfall-background-ticks";
+    waterfall.setAttribute("align", "center");
     waterfall.setAttribute("flex", "1");
 
     let start = (marker.start - timeStart) * dataScale;
     let width = (marker.end - marker.start) * dataScale;
     let offset = this._isRTL ? this._waterfallWidth : 0;
 
     let bar = this._document.createElement("hbox");
-    bar.className = "timeline-marker-bar";
+    bar.className = "waterfall-marker-bar";
     bar.style.backgroundColor = blueprint.fill;
     bar.style.borderColor = blueprint.stroke;
     bar.style.transform = "translateX(" + (start - offset) + "px)";
     bar.setAttribute("type", marker.name);
-    bar.setAttribute("width", Math.max(width, TIMELINE_MARKER_BAR_WIDTH_MIN));
+    bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));
     waterfall.appendChild(bar);
 
     container.appendChild(waterfall);
   },
 
   /**
    * Creates a dummy spacer as an empty marker.
    *
    * @param nsIDOMNode container
    *        The container node representing the marker.
    */
   _buildMarkerSpacer: function(container) {
     let sidebarSpacer = this._document.createElement("spacer");
-    sidebarSpacer.className = "timeline-marker-sidebar theme-sidebar";
-    sidebarSpacer.setAttribute("width", TIMELINE_MARKER_SIDEBAR_WIDTH);
+    sidebarSpacer.className = "waterfall-sidebar theme-sidebar";
+    sidebarSpacer.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
 
     let waterfallSpacer = this._document.createElement("spacer");
-    waterfallSpacer.className = "timeline-marker-waterfall";
+    waterfallSpacer.className = "waterfall-marker-item waterfall-background-ticks";
     waterfallSpacer.setAttribute("flex", "1");
 
     container.appendChild(sidebarSpacer);
     container.appendChild(waterfallSpacer);
   },
 
   /**
    * Creates the background displayed on the marker's waterfall.
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/timeline.dtd
@@ -10,20 +10,29 @@
   - You want to make that choice consistent across the developer tools.
   - A good criteria is the language in which you'd find the best
   - documentation on web development on the web. -->
 
 <!-- LOCALIZATION NOTE (timelineUI.recordButton): This string is displayed
   -  on a button that starts a new recording. -->
 <!ENTITY timelineUI.recordButton.tooltip "Record timeline operations">
 
-<!-- LOCALIZATION NOTE (timelineUI.recordButton): This string is displayed
+<!-- LOCALIZATION NOTE (timelineUI.recordLabel): This string is displayed
   -  as a label to signal that a recording is in progress. -->
 <!ENTITY timelineUI.recordLabel "Recording…">
 
+<!-- LOCALIZATION NOTE (timelineUI.timelineUI.memoryCheckbox.label): This string
+  -  is displayed next to a checkbox determining whether or not memory
+  -  measurements are enabled. -->
+<!ENTITY timelineUI.memoryCheckbox.label "Memory">
+
+<!-- LOCALIZATION NOTE (timelineUI.timelineUI.memoryCheckbox.tooltip): This string
+  -  is displayed next to the memory checkbox -->
+<!ENTITY timelineUI.memoryCheckbox.tooltip "Enable memory measurements">
+
 <!-- LOCALIZATION NOTE (timelineUI.emptyNotice1/2): This is the label shown
   -  in the timeline view when empty. -->
 <!ENTITY timelineUI.emptyNotice1    "Click on the">
 <!ENTITY timelineUI.emptyNotice2    "button to start recording timeline events.">
 
 <!-- LOCALIZATION NOTE (timelineUI.stopNotice1/2): This is the label shown
   -  in the timeline view while recording. -->
 <!ENTITY timelineUI.stopNotice1    "Click on the">
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/timeline.properties
@@ -36,13 +36,19 @@ timeline.records=RECORDS
 # LOCALIZATION NOTE (timeline.label.*):
 # These strings are displayed in the timeline waterfall, identifying markers.
 timeline.label.styles=Styles
 timeline.label.reflow=Reflow
 timeline.label.paint=Paint
 timeline.label.domevent=DOM Event
 timeline.label.consoleTime=Console
 
+# LOCALIZATION NOTE (graphs.memory):
+# This string is displayed in the memory graph of the Performance tool,
+# as the unit used to memory consumption. This label should be kept
+# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
+graphs.memory=MB
+
 # LOCALIZATION NOTE (timeline.markerDetailFormat):
 # Some timeline markers come with details, like a size, a name, a js function.
 # %1$S is replaced with one of the above label (timeline.label.*) and %2$S
 # with the details. For examples: Paint (200x100), or console.time (FOO)
 timeline.markerDetailFormat=%1$S (%2$S)
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -41,17 +41,17 @@ invitee_expire_hours_label=Invitation wi
 display_name_guest=Guest
 display_name_dnd_status=Do Not Disturb
 display_name_available_status=Available
 
 # Error bars
 ## LOCALIZATION NOTE(unable_retrieve_url,session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel here:
 ## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error
-unable_retrieve_url=Sorry, we were unable to retrieve a call url.
+unable_retrieve_url=Sorry, we were unable to retrieve a call URL.
 session_expired_error_description=Session expired. All URLs you have previously created and shared will no longer work.
 could_not_authenticate=Could Not Authenticate
 password_changed_question=Did you change your password?
 try_again_later=Please try again later
 could_not_connect=Could Not Connect To The Server
 check_internet_connection=Please check your internet connection
 login_expired=Your Login Has Expired
 service_not_available=Service Unavailable At This Time
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -20,16 +20,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 
 
+// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
+const PREF_LOG_LEVEL      = "browser.uitour.loglevel";
 const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
 const MAX_BUTTONS         = 4;
 
 const BUCKET_NAME         = "UITour";
 const BUCKET_TIMESTEPS    = [
   1 * 60 * 1000, // Until 1 minute after tab is closed/inactive.
   3 * 60 * 1000, // Until 3 minutes after tab is closed/inactive.
   10 * 60 * 1000, // Until 10 minutes after tab is closed/inactive.
@@ -37,16 +39,26 @@ const BUCKET_TIMESTEPS    = [
 ];
 
 // Time after which seen Page IDs expire.
 const SEENPAGEID_EXPIRY  = 8 * 7 * 24 * 60 * 60 * 1000; // 8 weeks.
 
 // Prefix for any target matching a search engine.
 const TARGET_SEARCHENGINE_PREFIX = "searchEngine-";
 
+// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let ConsoleAPI = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).ConsoleAPI;
+  let consoleOptions = {
+    // toLowerCase is because the loglevel values use title case to be compatible with Log.jsm.
+    maxLogLevel: Services.prefs.getCharPref(PREF_LOG_LEVEL).toLowerCase(),
+    prefix: "UITour",
+  };
+  return new ConsoleAPI(consoleOptions);
+});
 
 this.UITour = {
   url: null,
   seenPageIDs: null,
   pageIDSourceTabs: new WeakMap(),
   pageIDSourceWindows: new WeakMap(),
   /* Map from browser windows to a set of tabs in which a tour is open */
   originTabs: new WeakMap(),
@@ -136,16 +148,17 @@ this.UITour = {
     }],
     ["urlbar",      {
       query: "#urlbar",
       widgetName: "urlbar-container",
     }],
   ]),
 
   init: function() {
+    log.debug("Initializing UITour");
     // Lazy getter is initialized here so it can be replicated any time
     // in a test.
     delete this.seenPageIDs;
     Object.defineProperty(this, "seenPageIDs", {
       get: this.restoreSeenPageIDs.bind(this),
       configurable: true,
     });
 
@@ -219,99 +232,113 @@ this.UITour = {
     }
 
     Services.prefs.setCharPref(PREF_SEENPAGEIDS,
                                JSON.stringify([...this.seenPageIDs]));
   },
 
   onPageEvent: function(aMessage, aEvent) {
     let contentDocument = null;
-
     let browser = aMessage.target;
     let window = browser.ownerDocument.defaultView;
     let tab = window.gBrowser.getTabForBrowser(browser);
     let messageManager = browser.messageManager;
 
-    if (typeof aEvent.detail != "object")
+    log.debug("onPageEvent:", aEvent.detail);
+
+    if (typeof aEvent.detail != "object") {
+      log.warn("Malformed event - detail not an object");
       return false;
+    }
 
     let action = aEvent.detail.action;
-    if (typeof action != "string" || !action)
+    if (typeof action != "string" || !action) {
+      log.warn("Action not defined");
       return false;
+    }
 
     let data = aEvent.detail.data;
-    if (typeof data != "object")
+    if (typeof data != "object") {
+      log.warn("Malformed event - data not an object");
       return false;
+    }
 
     // Do this before bailing if there's no tab, so later we can pick up the pieces:
     window.gBrowser.tabContainer.addEventListener("TabSelect", this);
 
     if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
       contentDocument = browser.contentWindow.document;
       if (!tab) {
         // This should only happen while detaching a tab:
         if (this._detachingTab) {
+          log.debug("Got event while detatching a tab");
           this._queuedEvents.push(aEvent);
           this._pendingDoc = Cu.getWeakReference(contentDocument);
           return;
         }
-        Cu.reportError("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
+        log.error("Discarding tabless UITour event (" + action + ") while not detaching a tab." +
                        "This shouldn't happen!");
         return;
       }
     }
 
     switch (action) {
       case "registerPageID": {
         // This is only relevant if Telemtry is enabled.
-        if (!UITelemetry.enabled)
+        if (!UITelemetry.enabled) {
+          log.debug("registerPageID: Telemery disabled, not doing anything");
           break;
+        }
 
         // We don't want to allow BrowserUITelemetry.BUCKET_SEPARATOR in the
         // pageID, as it could make parsing the telemetry bucket name difficult.
-        if (typeof data.pageID == "string" &&
-            !data.pageID.contains(BrowserUITelemetry.BUCKET_SEPARATOR)) {
-          this.addSeenPageID(data.pageID);
+        if (typeof data.pageID != "string" ||
+            data.pageID.contains(BrowserUITelemetry.BUCKET_SEPARATOR)) {
+          log.warn("registerPageID: Invalid page ID specified");
+          break;
+        }
+
+        this.addSeenPageID(data.pageID);
 
-          // Store tabs and windows separately so we don't need to loop over all
-          // tabs when a window is closed.
-          this.pageIDSourceTabs.set(tab, data.pageID);
-          this.pageIDSourceWindows.set(window, data.pageID);
+        // Store tabs and windows separately so we don't need to loop over all
+        // tabs when a window is closed.
+        this.pageIDSourceTabs.set(tab, data.pageID);
+        this.pageIDSourceWindows.set(window, data.pageID);
 
-          this.setTelemetryBucket(data.pageID);
-        }
+        this.setTelemetryBucket(data.pageID);
+
         break;
       }
 
       case "showHighlight": {
         let targetPromise = this.getTarget(window, data.target);
         targetPromise.then(target => {
           if (!target.node) {
-            Cu.reportError("UITour: Target could not be resolved: " + data.target);
+            log.error("UITour: Target could not be resolved: " + data.target);
             return;
           }
           let effect = undefined;
           if (this.highlightEffects.indexOf(data.effect) !== -1) {
             effect = data.effect;
           }
           this.showHighlight(target, effect);
-        }).then(null, Cu.reportError);
+        }).catch(log.error);
         break;
       }
 
       case "hideHighlight": {
         this.hideHighlight(window);
         break;
       }
 
       case "showInfo": {
         let targetPromise = this.getTarget(window, data.target, true);
         targetPromise.then(target => {
           if (!target.node) {
-            Cu.reportError("UITour: Target could not be resolved: " + data.target);
+            log.error("UITour: Target could not be resolved: " + data.target);
             return;
           }
 
           let iconURL = null;
           if (typeof data.icon == "string")
             iconURL = this.resolveURL(browser, data.icon);
 
           let buttons = [];
@@ -328,31 +355,33 @@ this.UITour = {
                 if (typeof buttonData.icon == "string")
                   button.iconURL = this.resolveURL(browser, buttonData.icon);
 
                 if (typeof buttonData.style == "string")
                   button.style = buttonData.style;
 
                 buttons.push(button);
 
-                if (buttons.length == MAX_BUTTONS)
+                if (buttons.length == MAX_BUTTONS) {
+                  log.warn("showInfo: Reached limit of allowed number of buttons");
                   break;
+                }
               }
             }
           }
 
           let infoOptions = {};
 
           if (typeof data.closeButtonCallbackID == "string")
             infoOptions.closeButtonCallbackID = data.closeButtonCallbackID;
           if (typeof data.targetCallbackID == "string")
             infoOptions.targetCallbackID = data.targetCallbackID;
 
           this.showInfo(messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
-        }).then(null, Cu.reportError);
+        }).catch(log.error);
         break;
       }
 
       case "hideInfo": {
         this.hideInfo(window);
         break;
       }
 
@@ -387,46 +416,50 @@ this.UITour = {
       case "hideMenu": {
         this.hideMenu(window, data.name);
         break;
       }
 
       case "startUrlbarCapture": {
         if (typeof data.text != "string" || !data.text ||
             typeof data.url != "string" || !data.url) {
+          log.warn("startUrlbarCapture: Text or URL not specified");
           return false;
         }
 
         let uri = null;
         try {
           uri = Services.io.newURI(data.url, null, null);
         } catch (e) {
+          log.warn("startUrlbarCapture: Malformed URL specified");
           return false;
         }
 
         let secman = Services.scriptSecurityManager;
         let principal = contentDocument.nodePrincipal;
         let flags = secman.DISALLOW_INHERIT_PRINCIPAL;
         try {
           secman.checkLoadURIWithPrincipal(principal, uri, flags);
         } catch (e) {
+          log.warn("startUrlbarCapture: Orginating page doesn't have permission to open specified URL");
           return false;
         }
 
         this.startUrlbarCapture(window, data.text, data.url);
         break;
       }
 
       case "endUrlbarCapture": {
         this.endUrlbarCapture(window);
         break;
       }
 
       case "getConfiguration": {
         if (typeof data.configuration != "string") {
+          log.warn("getConfiguration: No configuration option specified");
           return false;
         }
 
         this.getConfiguration(messageManager, window, data.configuration, data.callbackID);
         break;
       }
 
       case "showFirefoxAccounts": {
@@ -443,17 +476,17 @@ this.UITour = {
         break;
       }
 
       case "addNavBarWidget": {
         // Add a widget to the toolbar
         let targetPromise = this.getTarget(window, data.name);
         targetPromise.then(target => {
           this.addNavBarWidget(target, messageManager, data.callbackID);
-        }).then(null, Cu.reportError);
+        }).catch(log.error);
         break;
       }
     }
 
     if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
       if (!this.originTabs.has(window)) {
         this.originTabs.set(window, new Set());
       }
@@ -524,17 +557,17 @@ this.UITour = {
             }
             this.originTabs.get(window).add(selectedTab);
             this.pendingDoc = null;
             this._detachingTab = false;
             while (this._queuedEvents.length) {
               try {
                 this.onPageEvent(this._queuedEvents.shift());
               } catch (ex) {
-                Cu.reportError(ex);
+                log.error(ex);
               }
             }
             break;
           }
         }
 
         this.teardownTour(window);
         break;
@@ -578,16 +611,17 @@ this.UITour = {
   // can remain lazy-loaded on-demand.
   getTelemetry: function() {
     return {
       seenPageIDs: [...this.seenPageIDs.keys()],
     };
   },
 
   teardownTour: function(aWindow, aWindowClosing = false) {
+    log.debug("teardownTour: aWindowClosing = " + aWindowClosing);
     aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
     aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
     aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
     aWindow.removeEventListener("SSWindowClosing", this);
 
     let originTabs = this.originTabs.get(aWindow);
     if (originTabs) {
       for (let tab of originTabs) {
@@ -622,18 +656,20 @@ this.UITour = {
   },
 
   // This function is copied to UITourListener.
   isSafeScheme: function(aURI) {
     let allowedSchemes = new Set(["https", "about"]);
     if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
       allowedSchemes.add("http");
 
-    if (!allowedSchemes.has(aURI.scheme))
+    if (!allowedSchemes.has(aURI.scheme)) {
+      log.error("Unsafe scheme:", aURI.scheme);
       return false;
+    }
 
     return true;
   },
 
   resolveURL: function(aBrowser, aURL) {
     try {
       let uri = Services.io.newURI(aURL, null, aBrowser.currentURI);
 
@@ -643,27 +679,30 @@ this.UITour = {
       return uri.spec;
     } catch (e) {}
 
     return null;
   },
 
   sendPageCallback: function(aMessageManager, aCallbackID, aData = {}) {
     let detail = {data: aData, callbackID: aCallbackID};
+    log.debug("sendPageCallback", detail);
     aMessageManager.sendAsyncMessage("UITour:SendPageCallback", detail);
   },
 
   isElementVisible: function(aElement) {
     let targetStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
     return (targetStyle.display != "none" && targetStyle.visibility == "visible");
   },
 
   getTarget: function(aWindow, aTargetName, aSticky = false) {
+    log.debug("getTarget:", aTargetName);
     let deferred = Promise.defer();
     if (typeof aTargetName != "string" || !aTargetName) {
+      log.warn("getTarget: Invalid target name specified");
       deferred.reject("Invalid target name specified");
       return deferred.promise;
     }
 
     if (aTargetName == "pinnedTab") {
       deferred.resolve({
           targetName: aTargetName,
           node: this.ensurePinnedTab(aWindow, aSticky)
@@ -673,42 +712,44 @@ this.UITour = {
 
     if (aTargetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
       let engineID = aTargetName.slice(TARGET_SEARCHENGINE_PREFIX.length);
       return this.getSearchEngineTarget(aWindow, engineID);
     }
 
     let targetObject = this.targets.get(aTargetName);
     if (!targetObject) {
+      log.warn("getTarget: The specified target name is not in the allowed set");
       deferred.reject("The specified target name is not in the allowed set");
       return deferred.promise;
     }
 
     let targetQuery = targetObject.query;
     aWindow.PanelUI.ensureReady().then(() => {
       let node;
       if (typeof targetQuery == "function") {
         try {
           node = targetQuery(aWindow.document);
         } catch (ex) {
+          log.warn("getTarget: Error running target query:", ex);
           node = null;
         }
       } else {
         node = aWindow.document.querySelector(targetQuery);
       }
 
       deferred.resolve({
         addTargetListener: targetObject.addTargetListener,
         node: node,
         removeTargetListener: targetObject.removeTargetListener,
         targetName: aTargetName,
         widgetName: targetObject.widgetName,
         allowAdd: targetObject.allowAdd,
       });
-    }).then(null, Cu.reportError);
+    }).catch(log.error);
     return deferred.promise;
   },
 
   targetIsInAppMenu: function(aTarget) {
     let placement = CustomizableUI.getPlacementOfWidget(aTarget.widgetName || aTarget.node.id);
     if (placement && placement.area == CustomizableUI.AREA_PANEL) {
       return true;
     }
@@ -724,41 +765,48 @@ this.UITour = {
              && targetElement.id != "PanelUI-button";
   },
 
   /**
    * Called before opening or after closing a highlight or info panel to see if
    * we need to open or close the appMenu to see the annotation's anchor.
    */
   _setAppMenuStateForAnnotation: function(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
+    log.debug("_setAppMenuStateForAnnotation:", aAnnotationType);
+    log.debug("_setAppMenuStateForAnnotation: Menu is exptected to be:", aShouldOpenForHighlight ? "open" : "closed");
+
     // If the panel is in the desired state, we're done.
     let panelIsOpen = aWindow.PanelUI.panel.state != "closed";
     if (aShouldOpenForHighlight == panelIsOpen) {
+      log.debug("_setAppMenuStateForAnnotation: Panel already in expected state");
       if (aCallback)
         aCallback();
       return;
     }
 
     // Don't close the menu if it wasn't opened by us (e.g. via showmenu instead).
     if (!aShouldOpenForHighlight && !this.appMenuOpenForAnnotation.has(aAnnotationType)) {
+      log.debug("_setAppMenuStateForAnnotation: Menu not opened by us, not closing");
       if (aCallback)
         aCallback();
       return;
     }
 
     if (aShouldOpenForHighlight) {
       this.appMenuOpenForAnnotation.add(aAnnotationType);
     } else {
       this.appMenuOpenForAnnotation.delete(aAnnotationType);
     }
 
     // Actually show or hide the menu
     if (this.appMenuOpenForAnnotation.size) {
+      log.debug("_setAppMenuStateForAnnotation: Opening the menu");
       this.showMenu(aWindow, "appMenu", aCallback);
     } else {
+      log.debug("_setAppMenuStateForAnnotation: Closing the menu");
       this.hideMenu(aWindow, "appMenu");
       if (aCallback)
         aCallback();
     }
 
   },
 
   previewTheme: function(aTheme) {
@@ -865,16 +913,17 @@ this.UITour = {
         highlighter.style.borderRadius = "";
       }
 
       highlighter.style.height = highlightHeight + "px";
       highlighter.style.width = highlightWidth + "px";
 
       // Close a previous highlight so we can relocate the panel.
       if (highlighter.parentElement.state == "showing" || highlighter.parentElement.state == "open") {
+        log.debug("showHighlight: Closing previous highlight first");
         highlighter.parentElement.hidePopup();
       }
       /* The "overlap" position anchors from the top-left but we want to centre highlights at their
          minimum size. */
       let highlightWindow = aTarget.node.ownerDocument.defaultView;
       let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
       let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
       let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
@@ -885,18 +934,20 @@ this.UITour = {
                       - (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
       let offsetY = paddingLeftPx
                       - (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
       this._addAnnotationPanelMutationObserver(highlighter.parentElement);
       highlighter.parentElement.openPopup(highlightAnchor, "overlap", offsetX, offsetY);
     }
 
     // Prevent showing a panel at an undefined position.
-    if (!this.isElementVisible(aTarget.node))
+    if (!this.isElementVisible(aTarget.node)) {
+      log.warn("showHighlight: Not showing a highlight since the target isn't visible", aTarget);
       return;
+    }
 
     this._setAppMenuStateForAnnotation(aTarget.node.ownerDocument.defaultView, "highlight",
                                        this.targetIsInAppMenu(aTarget),
                                        showHighlightPanel.bind(this));
   },
 
   hideHighlight: function(aWindow) {
     let tabData = this.pinnedTabs.get(aWindow);
@@ -1015,16 +1066,22 @@ this.UITour = {
         }
       });
 
       tooltip.setAttribute("targetName", aAnchor.targetName);
       tooltip.hidden = false;
       let alignment = "bottomcenter topright";
       this._addAnnotationPanelMutationObserver(tooltip);
       tooltip.openPopup(aAnchorEl, alignment);
+      if (tooltip.state == "closed") {
+        document.defaultView.addEventListener("endmodalstate", function endModalStateHandler() {
+          document.defaultView.removeEventListener("endmodalstate", endModalStateHandler);
+          tooltip.openPopup(aAnchorEl, alignment);
+        }, false);
+      }
     }
 
     // Prevent showing a panel at an undefined position.
     if (!this.isElementVisible(aAnchor.node))
       return;
 
     // Due to a platform limitation, we can't anchor a panel to an element in a
     // <menupopup>. So we can't support showing info panels for search engines.
@@ -1078,17 +1135,17 @@ this.UITour = {
       }
       aWindow.PanelUI.show();
     } else if (aMenuName == "bookmarks") {
       let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
       openMenuButton(menuBtn);
     } else if (aMenuName == "searchEngines") {
       this.getTarget(aWindow, "searchProvider").then(target => {
         openMenuButton(target.node);
-      }).catch(Cu.reportError);
+      }).catch(log.error);
     }
   },
 
   hideMenu: function(aWindow, aMenuName) {
     function closeMenuButton(aMenuBtn) {
       if (aMenuBtn && aMenuBtn.boxObject)
         aMenuBtn.boxObject.openMenu(false);
     }
@@ -1120,17 +1177,17 @@ this.UITour = {
           // Since getTarget is async, we need to make sure that the target hasn't
           // changed since it may have just moved to somewhere outside of the app menu.
           if (annotationElement.getAttribute("targetName") != aTarget.targetName ||
               annotationElement.state == "closed" ||
               !UITour.targetIsInAppMenu(aTarget)) {
             return;
           }
           hideMethod(win);
-        }).then(null, Cu.reportError);
+        }).catch(log.error);
       }
     });
     UITour.appMenuOpenForAnnotation.clear();
   },
 
   recreatePopup: function(aPanel) {
     // After changing popup attributes that relate to how the native widget is created
     // (e.g. @noautohide) we need to re-create the frame/widget for it to take effect.
@@ -1192,26 +1249,27 @@ this.UITour = {
         break;
       case "appinfo":
         let props = ["defaultUpdateChannel", "version"];
         let appinfo = {};
         props.forEach(property => appinfo[property] = Services.appinfo[property]);
         this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
         break;
       default:
-        Cu.reportError("getConfiguration: Unknown configuration requested: " + aConfiguration);
+        log.error("getConfiguration: Unknown configuration requested: " + aConfiguration);
         break;
     }
   },
 
   getAvailableTargets: function(aMessageManager, aChromeWindow, aCallbackID) {
     Task.spawn(function*() {
       let window = aChromeWindow;
       let data = this.availableTargetsCache.get(window);
       if (data) {
+        log.debug("getAvailableTargets: Using cached targets list", data.targets.join(","));
         this.sendPageCallback(aMessageManager, aCallbackID, data);
         return;
       }
 
       let promises = [];
       for (let targetName of this.targets.keys()) {
         promises.push(this.getTarget(window, targetName));
       }
@@ -1231,34 +1289,34 @@ this.UITour = {
       );
 
       data = {
         targets: targetNames,
       };
       this.availableTargetsCache.set(window, data);
       this.sendPageCallback(aMessageManager, aCallbackID, data);
     }.bind(this)).catch(err => {
-      Cu.reportError(err);
+      log.error(err);
       this.sendPageCallback(aMessageManager, aCallbackID, {
         targets: [],
       });
     });
   },
 
   addNavBarWidget: function (aTarget, aMessageManager, aCallbackID) {
     if (aTarget.node) {
-      Cu.reportError("UITour: can't add a widget already present: " + data.target);
+      log.error("UITour: can't add a widget already present: " + data.target);
       return;
     }
     if (!aTarget.allowAdd) {
-      Cu.reportError("UITour: not allowed to add this widget: " + data.target);
+      log.error("UITour: not allowed to add this widget: " + data.target);
       return;
     }
     if (!aTarget.widgetName) {
-      Cu.reportError("UITour: can't add a widget without a widgetName property: " + data.target);
+      log.error("UITour: can't add a widget without a widgetName property: " + data.target);
       return;
     }
 
     CustomizableUI.addWidgetToArea(aTarget.widgetName, CustomizableUI.AREA_NAVBAR);
     this.sendPageCallback(aMessageManager, aCallbackID);
   },
 
   _addAnnotationPanelMutationObserver: function(aPanelEl) {
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -23,16 +23,19 @@ skip-if = e10s # Bug 941428 - UITour.jsm
 [browser_UITour3.js]
 skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
+[browser_UITour_modalDialog.js]
+run-if = os == "mac" # modal dialog disabling only working on OS X
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_panel_close_annotation.js]
 skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
 [browser_UITour_registerPageID.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_sync.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_resetProfile.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_UITour_modalDialog.js
@@ -0,0 +1,106 @@
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+let handleDialog;
+
+// Modified from toolkit/components/passwordmgr/test/prompt_common.js
+var didDialog;
+
+var timer; // keep in outer scope so it's not GC'd before firing
+function startCallbackTimer() {
+    didDialog = false;
+
+    // Delay before the callback twiddles the prompt.
+    const dialogDelay = 10;
+
+    // Use a timer to invoke a callback to twiddle the authentication dialog
+    timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    timer.init(observer, dialogDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+
+var observer = SpecialPowers.wrapCallbackObject({
+    QueryInterface : function (iid) {
+        const interfaces = [Ci.nsIObserver,
+                            Ci.nsISupports, Ci.nsISupportsWeakReference];
+
+        if (!interfaces.some( function(v) { return iid.equals(v) } ))
+            throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+        return this;
+    },
+
+    observe : function (subject, topic, data) {
+        var doc = getDialogDoc();
+        if (doc)
+            handleDialog(doc);
+        else
+            startCallbackTimer(); // try again in a bit
+    }
+});
+
+function getDialogDoc() {
+  // Find the <browser> which contains notifyWindow, by looking
+  // through all the open windows and all the <browsers> in each.
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+           getService(Ci.nsIWindowMediator);
+  //var enumerator = wm.getEnumerator("navigator:browser");
+  var enumerator = wm.getXULWindowEnumerator(null);
+
+  while (enumerator.hasMoreElements()) {
+    var win = enumerator.getNext();
+    var windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+    var containedDocShells = windowDocShell.getDocShellEnumerator(
+                                      Ci.nsIDocShellTreeItem.typeChrome,
+                                      Ci.nsIDocShell.ENUMERATE_FORWARDS);
+    while (containedDocShells.hasMoreElements()) {
+        // Get the corresponding document for this docshell
+        var childDocShell = containedDocShells.getNext();
+        // We don't want it if it's not done loading.
+        if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+          continue;
+        var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+                                    .contentViewer
+                                    .DOMDocument;
+
+        //ok(true, "Got window: " + childDoc.location.href);
+        if (childDoc.location.href == "chrome://global/content/commonDialog.xul")
+          return childDoc;
+    }
+  }
+
+  return null;
+}
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+
+let tests = [
+  taskify(function* test_modal_dialog_while_opening_tooltip(done) {
+    let panelShown;
+    let popup;
+
+    handleDialog = (doc) => {
+      popup = document.getElementById("UITourTooltip");
+      gContentAPI.showInfo("appMenu", "test title", "test text");
+      doc.defaultView.setTimeout(function() {
+        is(popup.state, "closed", "Popup shouldn't be shown while dialog is up");
+        panelShown = promisePanelElementShown(window, popup);
+        let dialog = doc.getElementById("commonDialog");
+        dialog.acceptDialog();
+      }, 1000);
+    };
+    startCallbackTimer();
+    executeSoon(() => alert("test"));
+    yield waitForConditionPromise(() => panelShown, "Timed out waiting for panel promise to be assigned", 100);
+    yield panelShown;
+
+    yield hideInfoPromise();
+  })
+];
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -3,21 +3,21 @@
 
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource:///modules/UITour.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const SINGLE_TRY_TIMEOUT = 100;
 const NUMBER_OF_TRIES = 30;
 
-function waitForConditionPromise(condition, timeoutMsg) {
+function waitForConditionPromise(condition, timeoutMsg, tryCount=NUMBER_OF_TRIES) {
   let defer = Promise.defer();
   let tries = 0;
   function checkCondition() {
-    if (tries >= NUMBER_OF_TRIES) {
+    if (tries >= tryCount) {
       defer.reject(timeoutMsg);
     }
     var conditionPassed;
     try {
       conditionPassed = condition();
     } catch (e) {
       return defer.reject(e);
     }
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -239,17 +239,16 @@ browser.jar:
   skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css          (../shared/devtools/ruleview.css)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (../shared/devtools/images/webconsole.png)
   skin/classic/browser/devtools/webconsole@2x.png               (../shared/devtools/images/webconsole@2x.png)
   skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
-  skin/classic/browser/devtools/highlighter.css              (../shared/devtools/highlighter.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png       (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png  (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png)
   skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png)
   skin/classic/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -97,16 +97,22 @@
     margin-left: 7px;
   }
 }
 
 #main-window:not(:-moz-lwtheme) > #titlebar {
   -moz-appearance: -moz-window-titlebar;
 }
 
+@media (-moz-mac-yosemite-theme) {
+  #main-window:not(:-moz-lwtheme) > #titlebar {
+    -moz-appearance: -moz-mac-vibrancy-light;
+  }
+}
+
 #main-window:not([tabsintitlebar]) > #titlebar {
   height: 22px; /* The native titlebar on OS X is 22px tall. */
 }
 
 /**
  * For tabs in titlebar on OS X, we stretch the titlebar down so that the
  * tabstrip can overlap it.
  */
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -177,16 +177,17 @@ browser.jar:
 * skin/classic/browser/customizableui/panelUIOverlay.css    (customizableui/panelUIOverlay.css)
   skin/classic/browser/customizableui/whimsy.png          (../shared/customizableui/whimsy.png)
   skin/classic/browser/customizableui/whimsy@2x.png       (../shared/customizableui/whimsy@2x.png)
   skin/classic/browser/customizableui/whimsy-bw.png       (../shared/customizableui/whimsy-bw.png)
   skin/classic/browser/customizableui/whimsy-bw@2x.png    (../shared/customizableui/whimsy-bw@2x.png)
   skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/buttons.png                (downloads/buttons.png)
   skin/classic/browser/downloads/buttons@2x.png             (downloads/buttons@2x.png)
+* skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
   skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png)
   skin/classic/browser/downloads/download-notification-finish.png  (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-finish@2x.png  (downloads/download-notification-finish@2x.png)
   skin/classic/browser/downloads/download-notification-start.png  (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-notification-start@2x.png  (downloads/download-notification-start@2x.png)
   skin/classic/browser/downloads/download-summary.png       (downloads/download-summary.png)
   skin/classic/browser/downloads/download-summary@2x.png    (downloads/download-summary@2x.png)
@@ -356,17 +357,16 @@ browser.jar:
   skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
   skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
   skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
   skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png         (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css                (../shared/devtools/ruleview.css)
   skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
-  skin/classic/browser/devtools/highlighter.css              (../shared/devtools/highlighter.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png             (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png        (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-breakpoint@2x.png        (../shared/devtools/images/editor-breakpoint@2x.png)
   skin/classic/browser/devtools/editor-debug-location.png    (../shared/devtools/images/editor-debug-location.png)
   skin/classic/browser/devtools/editor-debug-location@2x.png    (../shared/devtools/images/editor-debug-location@2x.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
--- a/browser/themes/shared/devtools/timeline.inc.css
+++ b/browser/themes/shared/devtools/timeline.inc.css
@@ -43,117 +43,113 @@
   list-style-image: url(profiler-stopwatch-checked.svg);
 }
 
 #empty-notice button .button-text,
 #recording-notice button .button-text {
   display: none;
 }
 
-.theme-dark #timeline-overview {
-  border-bottom: 1px solid #000;
+.theme-dark #timeline-pane {
+  border-top: 1px solid #000;
 }
 
-.theme-light #timeline-overview {
-  border-bottom: 1px solid #aaa;
+.theme-light #timeline-pane {
+  border-top: 1px solid #aaa;
 }
 
-.timeline-list-contents {
+.waterfall-list-contents {
   /* Hack: force hardware acceleration */
   transform: translateZ(1px);
   overflow-x: hidden;
   overflow-y: auto;
 }
 
-.timeline-header-ticks,
-.timeline-marker-waterfall {
+.waterfall-background-ticks {
   /* Background created on a <canvas> in js. */
   /* @see browser/devtools/timeline/widgets/waterfall.js */
   background-image: -moz-element(#waterfall-background);
   background-repeat: repeat-y;
   background-position: -1px center;
 }
 
-.timeline-marker-waterfall {
-  overflow: hidden;
-}
-
-.timeline-marker-container[is-spacer] {
+.waterfall-marker-container[is-spacer] {
   pointer-events: none;
 }
 
-.theme-dark .timeline-marker-container:not([is-spacer]):nth-child(2n) {
+.theme-dark .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
   background-color: rgba(255,255,255,0.03);
 }
 
-.theme-light .timeline-marker-container:not([is-spacer]):nth-child(2n) {
+.theme-light .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
   background-color: rgba(128,128,128,0.03);
 }
 
-.theme-dark .timeline-marker-container:hover {
+.theme-dark .waterfall-marker-container:hover {
   background-color: rgba(255,255,255,0.1) !important;
 }
 
-.theme-light .timeline-marker-container:hover {
+.theme-light .waterfall-marker-container:hover {
   background-color: rgba(128,128,128,0.1) !important;
 }
 
-.timeline-header-sidebar,
-.timeline-marker-sidebar {
+.waterfall-marker-item {
+  overflow: hidden;
+}
+
+.waterfall-sidebar {
   -moz-border-end: 1px solid;
 }
 
-.theme-dark .timeline-header-sidebar,
-.theme-dark .timeline-marker-sidebar {
+.theme-dark .waterfall-sidebar {
   -moz-border-end-color: #000;
 }
 
-.theme-light .timeline-header-sidebar,
-.theme-light .timeline-marker-sidebar {
+.theme-light .waterfall-sidebar {
   -moz-border-end-color: #aaa;
 }
 
-.timeline-header-sidebar {
-  padding: 5px;
-}
-
-.timeline-marker-sidebar {
-  padding: 2px;
-}
-
-.timeline-marker-container:hover > .timeline-marker-sidebar {
+.waterfall-marker-container:hover > .waterfall-sidebar {
   background-color: transparent;
 }
 
-.timeline-header-tick {
+.waterfall-header-name {
+  padding: 4px;
+}
+
+.waterfall-header-tick {
   width: 100px;
   font-size: 9px;
   transform-origin: left center;
 }
 
-.theme-dark .timeline-header-tick {
+.theme-dark .waterfall-header-tick {
   color: #a9bacb;
 }
 
-.theme-light .timeline-header-tick {
+.theme-light .waterfall-header-tick {
   color: #292e33;
 }
 
-.timeline-header-tick:not(:first-child) {
+.waterfall-header-tick:not(:first-child) {
   -moz-margin-start: -100px !important; /* Don't affect layout. */
 }
 
-.timeline-marker-bullet {
+.waterfall-marker-bullet {
   width: 8px;
   height: 8px;
   -moz-margin-start: 8px;
   -moz-margin-end: 6px;
   border: 1px solid;
   border-radius: 1px;
 }
 
-.timeline-marker-bar {
-  margin-top: 4px;
-  margin-bottom: 4px;
+.waterfall-marker-name {
+  font-size: 95%;
+  padding-bottom: 1px !important;
+}
+
+.waterfall-marker-bar {
+  height: 9px;
   border: 1px solid;
   border-radius: 1px;
   transform-origin: left center;
 }
--- a/browser/themes/shared/devtools/widgets.inc.css
+++ b/browser/themes/shared/devtools/widgets.inc.css
@@ -933,36 +933,31 @@
 }
 
 .graph-widget-canvas[input=dragging-selection-contents] {
   cursor: grabbing;
 }
 
 /* Line graph widget */
 
-.line-graph-widget-canvas {
-  background: #0088cc;
-}
-
 .line-graph-widget-gutter {
   position: absolute;
   background: rgba(255,255,255,0.75);
   width: 10px;
   height: 100%;
   top: 0;
   left: 0;
   border-right: 1px solid rgba(255,255,255,0.25);
   pointer-events: none;
 }
 
 .line-graph-widget-gutter-line {
   position: absolute;
   width: 100%;
   border-top: 1px solid;
-  transform: translateY(-1px);
 }
 
 .line-graph-widget-gutter-line[type=maximum] {
   border-color: #2cbb0f;
 }
 
 .line-graph-widget-gutter-line[type=minimum] {
   border-color: #ed2655;
@@ -970,55 +965,66 @@
 
 .line-graph-widget-gutter-line[type=average] {
   border-color: #d97e00;
 }
 
 .line-graph-widget-tooltip {
   position: absolute;
   background: rgba(255,255,255,0.75);
-  box-shadow: 0 2px 1px rgba(0,0,0,0.1);
   border-radius: 2px;
   line-height: 15px;
   -moz-padding-start: 6px;
   -moz-padding-end: 6px;
   transform: translateY(-50%);
   font-size: 80%;
   z-index: 1;
   pointer-events: none;
 }
 
-.line-graph-widget-tooltip::before {
+.line-graph-widget-tooltip[with-arrows=true]::before {
   content: "";
   position: absolute;
   border-top: 3px solid transparent;
   border-bottom: 3px solid transparent;
   top: calc(50% - 3px);
 }
 
-.line-graph-widget-tooltip[arrow=start]::before {
+.line-graph-widget-tooltip[arrow=start][with-arrows=true]::before {
   -moz-border-end: 3px solid rgba(255,255,255,0.75);
   left: -3px;
 }
 
-.line-graph-widget-tooltip[arrow=end]::before {
+.line-graph-widget-tooltip[arrow=end][with-arrows=true]::before {
   -moz-border-start: 3px solid rgba(255,255,255,0.75);
   right: -3px;
 }
 
 .line-graph-widget-tooltip[type=maximum] {
-  left: calc(10px + 6px);
+  left: -1px;
 }
 
 .line-graph-widget-tooltip[type=minimum] {
-  left: calc(10px + 6px);
+  left: -1px;
 }
 
 .line-graph-widget-tooltip[type=average] {
-  right: 6px;
+  right: -1px;
+}
+
+.line-graph-widget-tooltip[type=maximum][with-arrows=true] {
+  left: 14px;
+}
+
+.line-graph-widget-tooltip[type=minimum][with-arrows=true] {
+  left: 14px;
+}
+
+.line-graph-widget-tooltip[type=average][with-arrows=true] {
+  right: 4px;
 }
 
 .line-graph-widget-tooltip > [text=info] {
   color: #18191a;
 }
 
 .line-graph-widget-tooltip > [text=value] {
   -moz-margin-start: 3px;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -270,17 +270,16 @@ browser.jar:
         skin/classic/browser/devtools/command-pick.png              (../shared/devtools/images/command-pick.png)
         skin/classic/browser/devtools/command-pick@2x.png           (../shared/devtools/images/command-pick@2x.png)
         skin/classic/browser/devtools/command-frames.png            (../shared/devtools/images/command-frames.png)
         skin/classic/browser/devtools/command-frames@2x.png         (../shared/devtools/images/command-frames@2x.png)
         skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
         skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
         skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
         skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
-        skin/classic/browser/devtools/highlighter.css               (../shared/devtools/highlighter.css)
         skin/classic/browser/devtools/markup-view.css               (../shared/devtools/markup-view.css)
         skin/classic/browser/devtools/editor-error.png              (../shared/devtools/images/editor-error.png)
         skin/classic/browser/devtools/editor-breakpoint.png         (../shared/devtools/images/editor-breakpoint.png)
         skin/classic/browser/devtools/editor-breakpoint@2x.png         (../shared/devtools/images/editor-breakpoint@2x.png)
         skin/classic/browser/devtools/editor-debug-location.png     (../shared/devtools/images/editor-debug-location.png)
         skin/classic/browser/devtools/editor-debug-location@2x.png     (../shared/devtools/images/editor-debug-location@2x.png)
 *       skin/classic/browser/devtools/webconsole.css                (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css   (devtools/webconsole_networkpanel.css)
@@ -707,17 +706,16 @@ browser.jar:
         skin/classic/aero/browser/devtools/command-console.png       (../shared/devtools/images/command-console.png)
         skin/classic/aero/browser/devtools/command-console@2x.png    (../shared/devtools/images/command-console@2x.png)
         skin/classic/aero/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
         skin/classic/aero/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (../shared/devtools/images/alerticon-warning.png)
         skin/classic/aero/browser/devtools/alerticon-warning@2x.png  (../shared/devtools/images/alerticon-warning@2x.png)
 *       skin/classic/aero/browser/devtools/ruleview.css              (../shared/devtools/ruleview.css)
         skin/classic/aero/browser/devtools/commandline.css           (devtools/commandline.css)
-        skin/classic/aero/browser/devtools/highlighter.css           (../shared/devtools/highlighter.css)
         skin/classic/aero/browser/devtools/markup-view.css           (../shared/devtools/markup-view.css)
         skin/classic/aero/browser/devtools/editor-error.png           (../shared/devtools/images/editor-error.png)
         skin/classic/aero/browser/devtools/editor-breakpoint.png      (../shared/devtools/images/editor-breakpoint.png)
         skin/classic/aero/browser/devtools/editor-breakpoint@2x.png      (../shared/devtools/images/editor-breakpoint@2x.png)
         skin/classic/aero/browser/devtools/editor-debug-location.png  (../shared/devtools/images/editor-debug-location.png)
         skin/classic/aero/browser/devtools/editor-debug-location@2x.png  (../shared/devtools/images/editor-debug-location@2x.png)
 *       skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/aero/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8774,16 +8774,26 @@ nsGlobalWindow::LeaveModalState()
       mSuspendedDoc = nullptr;
     }
   }
 
   // Remember the time of the last dialog quit.
   nsGlobalWindow *inner = topWin->GetCurrentInnerWindowInternal();
   if (inner)
     inner->mLastDialogQuitTime = TimeStamp::Now();
+
+  if (topWin->mModalStateDepth == 0) {
+    nsCOMPtr<nsIDOMEvent> event;
+    NS_NewDOMEvent(getter_AddRefs(event), topWin, nullptr, nullptr);
+    event->InitEvent(NS_LITERAL_STRING("endmodalstate"), true, false);
+    event->SetTrusted(true);
+    event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
+    bool dummy;
+    topWin->DispatchEvent(event, &dummy);
+  }
 }
 
 bool
 nsGlobalWindow::IsInModalState()
 {
   nsGlobalWindow *topWin = GetScriptableTop();
 
   if (!topWin) {
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -428,25 +428,33 @@ nsGeolocationRequest::GetElement(nsIDOME
   NS_ENSURE_ARG_POINTER(aRequestingElement);
   *aRequestingElement = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::Cancel()
 {
+  if (mLocator->ClearPendingRequest(this)) {
+    return NS_OK;
+  }
+
   NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::Allow(JS::HandleValue aChoices)
 {
   MOZ_ASSERT(aChoices.isUndefined());
 
+  if (mLocator->ClearPendingRequest(this)) {
+    return NS_OK;
+  }
+
   // Kick off the geo device, if it isn't already running
   nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
   nsresult rv = gs->StartDevice(GetPrincipal());
 
   if (NS_FAILED(rv)) {
     // Location provider error
     NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
     return NS_OK;
@@ -1299,16 +1307,38 @@ Geolocation::NotifyError(uint16_t aError
   // notify everyone that is watching
   for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
     mWatchingCallbacks[i]->NotifyErrorAndShutdown(aErrorCode);
   }
 
   return NS_OK;
 }
 
+bool
+Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest)
+{
+  for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
+    if (mClearedWatchIDs[i] == aRequest->WatchId()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest)
+{
+  if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
+    this->NotifyAllowedRequest(aRequest);
+    this->ClearWatch(aRequest->WatchId());
+    return true;
+  }
+  return false;
+}
+
 void
 Geolocation::GetCurrentPosition(PositionCallback& aCallback,
                                 PositionErrorCallback* aErrorCallback,
                                 const PositionOptions& aOptions,
                                 ErrorResult& aRv)
 {
   GeoPositionCallback successCallback(&aCallback);
   GeoPositionErrorCallback errorCallback(aErrorCallback);
@@ -1484,20 +1514,25 @@ Geolocation::WatchPositionReady(nsGeoloc
 
 NS_IMETHODIMP
 Geolocation::ClearWatch(int32_t aWatchId)
 {
   if (aWatchId < 0) {
     return NS_OK;
   }
 
+  if (!mClearedWatchIDs.Contains(aWatchId)) {
+    mClearedWatchIDs.AppendElement(aWatchId);
+  }
+
   for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) {
     if (mWatchingCallbacks[i]->WatchId() == aWatchId) {
       mWatchingCallbacks[i]->Shutdown();
       RemoveRequest(mWatchingCallbacks[i]);
+      mClearedWatchIDs.RemoveElement(aWatchId);
       break;
     }
   }
 
   // make sure we also search through the pending requests lists for
   // watches to clear...
   for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) {
     if (mPendingRequests[i]->IsWatch() &&
--- a/dom/geolocation/nsGeolocation.h
+++ b/dom/geolocation/nsGeolocation.h
@@ -149,16 +149,20 @@ public:
   bool HasActiveCallbacks();
 
   // Register an allowed request
   void NotifyAllowedRequest(nsGeolocationRequest* aRequest);
 
   // Remove request from all callbacks arrays
   void RemoveRequest(nsGeolocationRequest* request);
 
+  // Check if there is already ClearWatch called for current
+  // request & clear if yes
+  bool ClearPendingRequest(nsGeolocationRequest* aRequest);
+
   // Shutting down.
   void Shutdown();
 
   // Getter for the principal that this Geolocation was loaded from
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
   // Getter for the window that this Geolocation is owned by
   nsIWeakReference* GetOwner() { return mOwner; }
@@ -180,16 +184,19 @@ private:
   nsresult WatchPosition(GeoPositionCallback& aCallback, GeoPositionErrorCallback& aErrorCallback, PositionOptions* aOptions, int32_t* aRv);
 
   bool RegisterRequestWithPrompt(nsGeolocationRequest* request);
 
   // Methods for the service when it's ready to process requests:
   nsresult GetCurrentPositionReady(nsGeolocationRequest* aRequest);
   nsresult WatchPositionReady(nsGeolocationRequest* aRequest);
 
+  // Check if clearWatch is already called
+  bool IsAlreadyCleared(nsGeolocationRequest* aRequest);
+
   // Two callback arrays.  The first |mPendingCallbacks| holds objects for only
   // one callback and then they are released/removed from the array.  The second
   // |mWatchingCallbacks| holds objects until the object is explictly removed or
   // there is a page change. All requests held by either array are active, that
   // is, they have been allowed and expect to be fulfilled.
 
   nsTArray<nsRefPtr<nsGeolocationRequest> > mPendingCallbacks;
   nsTArray<nsRefPtr<nsGeolocationRequest> > mWatchingCallbacks;
@@ -203,16 +210,19 @@ private:
   // owning back pointer.
   nsRefPtr<nsGeolocationService> mService;
 
   // Watch ID
   uint32_t mLastWatchId;
 
   // Pending requests are used when the service is not ready
   nsTArray<nsRefPtr<nsGeolocationRequest> > mPendingRequests;
+
+  // Array containing already cleared watch IDs
+  nsTArray<int32_t> mClearedWatchIDs;
 };
 
 class PositionError MOZ_FINAL : public nsIDOMGeoPositionError,
                                 public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PositionError)
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -336,20 +336,23 @@ already_AddRefed<DOMRequest>
 nsBrowserElement::Download(const nsAString& aUrl,
                            const BrowserElementDownloadOptions& aOptions,
                            ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
   NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
+  nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
+  MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
   AutoJSAPI jsapi;
-  jsapi.Init();
-  JS::Rooted<JS::Value> options(jsapi.cx());
-  if (!ToJSValue(jsapi.cx(), aOptions, &options)) {
+  jsapi.Init(wrappedObj->GetJSObject());
+  JSContext* cx = jsapi.cx();
+  JS::Rooted<JS::Value> options(cx);
+  if (!ToJSValue(cx, aOptions, &options)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
   nsresult rv = mBrowserElementAPI->Download(aUrl, options, getter_AddRefs(req));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
--- a/dom/phonenumberutils/PhoneNumberMetaData.jsm
+++ b/dom/phonenumberutils/PhoneNumberMetaData.jsm
@@ -9,26 +9,26 @@ this.PHONE_NUMBER_META_DATA = {
 "385": '["HR","00","0",,,"$NP$FG","\\d{6,12}","[1-7]\\d{5,8}|[89]\\d{6,11}",[["(1)(\\d{4})(\\d{3})","$1 $2 $3","1",,],["(6[09])(\\d{4})(\\d{3})","$1 $2 $3","6[09]",,],["(62)(\\d{3})(\\d{3,4})","$1 $2 $3","62",,],["([2-5]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[2-5]",,],["(9\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","9",,],["(9\\d)(\\d{4})(\\d{4})","$1 $2 $3","9",,],["(9\\d)(\\d{3,4})(\\d{3})(\\d{3})","$1 $2 $3 $4","9",,],["(\\d{2})(\\d{2})(\\d{2,3})","$1 $2 $3","6[145]|7",,],["(\\d{2})(\\d{3,4})(\\d{3})","$1 $2 $3","6[145]|7",,],["(80[01])(\\d{2})(\\d{2,3})","$1 $2 $3","8",,],["(80[01])(\\d{3,4})(\\d{3})","$1 $2 $3","8",,]]]',
 "670": '["TL","00",,,,,"\\d{7,8}","[2-489]\\d{6}|7\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[2-489]",,],["(\\d{4})(\\d{4})","$1 $2","7",,]]]',
 "258": '["MZ","00",,,,,"\\d{8,9}","[28]\\d{7,8}",[["([28]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","2|8[2-7]",,],["(80\\d)(\\d{3})(\\d{3})","$1 $2 $3","80",,]]]',
 "359": '["BG","00","0",,,"$NP$FG","\\d{5,9}","[23567]\\d{5,7}|[489]\\d{6,8}",[["(2)(\\d{5})","$1 $2","29",,],["(2)(\\d{3})(\\d{3,4})","$1 $2 $3","2",,],["(\\d{3})(\\d{4})","$1 $2","43[124-7]|70[1-9]",,],["(\\d{3})(\\d{3})(\\d{2})","$1 $2 $3","43[124-7]|70[1-9]",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","[78]00",,],["(\\d{2})(\\d{3})(\\d{2,3})","$1 $2 $3","[356]|4[124-7]|7[1-9]|8[1-6]|9[1-7]",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","48|8[7-9]|9[08]",,]]]',
 "682": '["CK","00",,,,,"\\d{5}","[2-57]\\d{4}",[["(\\d{2})(\\d{3})","$1 $2",,,]]]',
 "852": '["HK","00",,,,,"\\d{5,11}","[235-7]\\d{7}|8\\d{7,8}|9\\d{4,10}",[["(\\d{4})(\\d{4})","$1 $2","[235-7]|[89](?:0[1-9]|[1-9])",,],["(800)(\\d{3})(\\d{3})","$1 $2 $3","800",,],["(900)(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3 $4","900",,],["(900)(\\d{2,5})","$1 $2","900",,]]]',
 "998": '["UZ","810","8",,,"$NP $FG","\\d{7,9}","[679]\\d{8}",[["([679]\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "291": '["ER","00","0",,,"$NP$FG","\\d{6,7}","[178]\\d{6}",[["(\\d)(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
-"95": '["MM","00","0",,,"$NP$FG","\\d{5,10}","[14578]\\d{5,7}|[26]\\d{5,8}|9(?:2\\d{0,2}|[58]|3\\d|4\\d{1,2}|[679]\\d?)\\d{6}",[["(\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","1|2[45]",,],["(2)(\\d{4})(\\d{4})","$1 $2 $3","251",,],["(\\d)(\\d{2})(\\d{3})","$1 $2 $3","16|2",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","67|81",,],["(\\d{2})(\\d{2})(\\d{3,4})","$1 $2 $3","[4-8]",,],["(9)(\\d{3})(\\d{4,5})","$1 $2 $3","9(?:2[0-4]|[35-9]|4[13789])",,],["(9)(4\\d{4})(\\d{4})","$1 $2 $3","94[0245]",,],["(9)(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3 $4","925",,]]]',
+"95": '["MM","00","0",,,"$NP$FG","\\d{5,10}","[14578]\\d{5,7}|[26]\\d{5,8}|9(?:2\\d{0,2}|[58]|3\\d|4\\d{1,2}|6\\d?|[79]\\d{0,2})\\d{6}",[["(\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","1|2[45]",,],["(2)(\\d{4})(\\d{4})","$1 $2 $3","251",,],["(\\d)(\\d{2})(\\d{3})","$1 $2 $3","16|2",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","67|81",,],["(\\d{2})(\\d{2})(\\d{3,4})","$1 $2 $3","[4-8]",,],["(9)(\\d{3})(\\d{4,6})","$1 $2 $3","9(?:2[0-4]|[35-9]|4[13789])",,],["(9)(4\\d{4})(\\d{4})","$1 $2 $3","94[0245]",,],["(9)(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3 $4","925",,]]]',
 "266": '["LS","00",,,,,"\\d{8}","[2568]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
 "245": '["GW","00",,,,,"\\d{7}","[3-79]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
-"374": '["AM","00","0",,,"($NP$FG)","\\d{5,8}","[1-9]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2","1|47",,],["(\\d{2})(\\d{6})","$1 $2","[5-7]|9[1-9]","$NP$FG",],["(\\d{3})(\\d{5})","$1 $2","[23]",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","8|90","$NP $FG",]]]',
+"374": '["AM","00","0",,,"($NP$FG)","\\d{5,8}","[1-9]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2","1|47",,],["(\\d{2})(\\d{6})","$1 $2","4[139]|[5-7]|9[1-9]","$NP$FG",],["(\\d{3})(\\d{5})","$1 $2","[23]",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","8|90","$NP $FG",]]]',
 "379": '["VA","00",,,,,"\\d{10}","06\\d{8}",[["(06)(\\d{4})(\\d{4})","$1 $2 $3",,,]]]',
 "61": ['["AU","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",,,,"\\d{6,10}","[1-578]\\d{5,9}",[["([2378])(\\d{4})(\\d{4})","$1 $2 $3","[2378]","($NP$FG)",],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[45]|14","$NP$FG",],["(16)(\\d{3})(\\d{2,4})","$1 $2 $3","16","$NP$FG",],["(1[389]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","1(?:[38]0|90)","$FG",],["(180)(2\\d{3})","$1 $2","180","$FG",],["(19\\d)(\\d{3})","$1 $2","19[13]","$FG",],["(19\\d{2})(\\d{4})","$1 $2","19[67]","$FG",],["(13)(\\d{2})(\\d{2})","$1 $2 $3","13[1-9]","$FG",]]]','["CC","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",,,,"\\d{6,10}","[1458]\\d{5,9}",]','["CX","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",,,,"\\d{6,10}","[1458]\\d{5,9}",]'],
 "500": '["FK","00",,,,,"\\d{5}","[2-7]\\d{4}",]',
 "261": '["MG","00","0",,,"$NP$FG","\\d{7,9}","[23]\\d{8}",[["([23]\\d)(\\d{2})(\\d{3})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "92": '["PK","00","0",,,"($NP$FG)","\\d{6,12}","1\\d{8}|[2-8]\\d{5,11}|9(?:[013-9]\\d{4,9}|2\\d(?:111\\d{6}|\\d{3,7}))",[["(\\d{2})(111)(\\d{3})(\\d{3})","$1 $2 $3 $4","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)1",,],["(\\d{3})(111)(\\d{3})(\\d{3})","$1 $2 $3 $4","2[349]|45|54|60|72|8[2-5]|9[2-9]",,],["(\\d{2})(\\d{7,8})","$1 $2","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]",,],["(\\d{3})(\\d{6,7})","$1 $2","2[349]|45|54|60|72|8[2-5]|9[2-9]",,],["(3\\d{2})(\\d{7})","$1 $2","3","$NP$FG",],["([15]\\d{3})(\\d{5,6})","$1 $2","58[12]|1",,],["(586\\d{2})(\\d{5})","$1 $2","586",,],["([89]00)(\\d{3})(\\d{2})","$1 $2 $3","[89]00","$NP$FG",]]]',
-"234": '["NG","009","0",,,"$NP$FG","\\d{5,14}","[1-6]\\d{5,8}|9\\d{5,9}|[78]\\d{5,13}",[["([129])(\\d{3})(\\d{3,4})","$1 $2 $3","[129]",,],["(\\d{2})(\\d{3})(\\d{2,3})","$1 $2 $3","[3-6]|7(?:[1-79]|0[1-9])|8[2-9]",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","70|8[01]|90[39]",,],["([78]00)(\\d{4})(\\d{4,5})","$1 $2 $3","[78]00",,],["([78]00)(\\d{5})(\\d{5,6})","$1 $2 $3","[78]00",,],["(78)(\\d{2})(\\d{3})","$1 $2 $3","78",,]]]',
+"234": '["NG","009","0",,,"$NP$FG","\\d{5,14}","[1-6]\\d{5,8}|9\\d{5,9}|[78]\\d{5,13}",[["([129])(\\d{3})(\\d{3,4})","$1 $2 $3","[129]",,],["(\\d{2})(\\d{3})(\\d{2,3})","$1 $2 $3","[3-6]|7(?:[1-79]|0[1-9])|8[2-9]",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","70|8[01]|90[239]",,],["([78]00)(\\d{4})(\\d{4,5})","$1 $2 $3","[78]00",,],["([78]00)(\\d{5})(\\d{5,6})","$1 $2 $3","[78]00",,],["(78)(\\d{2})(\\d{3})","$1 $2 $3","78",,]]]',
 "350": '["GI","00",,,,,"\\d{8}","[2568]\\d{7}",[["(\\d{3})(\\d{5})","$1 $2","2",,]]]',
 "45": '["DK","00",,,,,"\\d{8}","[2-9]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "963": '["SY","00","0",,,"$NP$FG","\\d{6,9}","[1-59]\\d{7,8}",[["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","[1-5]",,],["(9\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","9",,]]]',
 "226": '["BF","00",,,,,"\\d{8}","[24-7]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "974": '["QA","00",,,,,"\\d{7,8}","[2-8]\\d{6,7}",[["([28]\\d{2})(\\d{4})","$1 $2","[28]",,],["([3-7]\\d{3})(\\d{4})","$1 $2","[3-7]",,]]]',
 "218": '["LY","00","0",,,"$NP$FG","\\d{7,9}","[25679]\\d{8}",[["([25679]\\d)(\\d{7})","$1-$2",,,]]]',
 "51": '["PE","19(?:1[124]|77|90)00","0",,,"($NP$FG)","\\d{6,9}","[14-9]\\d{7,8}",[["(1)(\\d{7})","$1 $2","1",,],["([4-8]\\d)(\\d{6})","$1 $2","[4-7]|8[2-4]",,],["(\\d{3})(\\d{5})","$1 $2","80",,],["(9\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","9","$FG",]]]',
 "62": '["ID","0(?:0[1789]|10(?:00|1[67]))","0",,,"$NP$FG","\\d{5,11}","[1-9]\\d{6,10}",[["(\\d{2})(\\d{5,8})","$1 $2","2[124]|[36]1","($NP$FG)",],["(\\d{3})(\\d{5,7})","$1 $2","[4579]|2[035-9]|[36][02-9]","($NP$FG)",],["(8\\d{2})(\\d{3,4})(\\d{3,4})","$1-$2-$3","8[1-35-9]",,],["(177)(\\d{6,8})","$1 $2","1",,],["(800)(\\d{5,7})","$1 $2","800",,],["(80\\d)(\\d)(\\d{3})(\\d{3})","$1 $2 $3 $4","80[79]",,]]]',
@@ -67,23 +67,23 @@ this.PHONE_NUMBER_META_DATA = {
 "672": '["NF","00",,,,,"\\d{5,6}","[13]\\d{5}",[["(\\d{2})(\\d{4})","$1 $2","1",,],["(\\d)(\\d{5})","$1 $2","3",,]]]',
 "870": '["001",,,,,,"\\d{9}","[35-7]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
 "883": '["001",,,,,,"\\d{9}(?:\\d{3})?","51\\d{7}(?:\\d{3})?",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","510",,],["(\\d{3})(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3 $4","510",,],["(\\d{4})(\\d{4})(\\d{4})","$1 $2 $3","51[13]",,]]]',
 "264": '["NA","00","0",,,"$NP$FG","\\d{8,9}","[68]\\d{7,8}",[["(8\\d)(\\d{3})(\\d{4})","$1 $2 $3","8[1235]",,],["(6\\d)(\\d{2,3})(\\d{4})","$1 $2 $3","6",,],["(88)(\\d{3})(\\d{3})","$1 $2 $3","88",,],["(870)(\\d{3})(\\d{3})","$1 $2 $3","870",,]]]',
 "878": '["001",,,,,,"\\d{12}","1\\d{11}",[["(\\d{2})(\\d{5})(\\d{5})","$1 $2 $3",,,]]]',
 "239": '["ST","00",,,,,"\\d{7}","[29]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
 "357": '["CY","00",,,,,"\\d{8}","[257-9]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2",,,]]]',
 "240": '["GQ","00",,,,,"\\d{9}","[23589]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[235]",,],["(\\d{3})(\\d{6})","$1 $2","[89]",,]]]',
-"506": '["CR","00",,"(19(?:0[01468]|19|20|66|77))",,,"\\d{8,10}","[24-9]\\d{7,9}",[["(\\d{4})(\\d{4})","$1 $2","[24-7]|8[3-9]",,],["(\\d{3})(\\d{3})(\\d{4})","$1-$2-$3","[89]0",,]]]',
-"86": '["CN","(1[1279]\\d{3})?00","0","(1[1279]\\d{3})|0",,,"\\d{4,12}","[1-7]\\d{6,11}|8[0-357-9]\\d{6,9}|9\\d{9}",[["(80\\d{2})(\\d{4})","$1 $2","80[2678]","$NP$FG",],["([48]00)(\\d{3})(\\d{4})","$1 $2 $3","[48]00",,],["(\\d{5,6})","$1","100|95",,"NA"],["(\\d{2})(\\d{5,6})","$1 $2","(?:10|2\\d)[19]","$NP$FG",],["(\\d{3})(\\d{5,6})","$1 $2","[3-9]","$NP$FG",],["(\\d{3,4})(\\d{4})","$1 $2","[2-9]",,"NA"],["(21)(\\d{4})(\\d{4,6})","$1 $2 $3","21","$NP$FG",],["([12]\\d)(\\d{4})(\\d{4})","$1 $2 $3","10[1-9]|2[02-9]","$NP$FG",],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","3(?:11|7[179])|4(?:[15]1|3[12])|5(?:1|2[37]|3[12]|51|7[13-79]|9[15])|7(?:31|5[457]|6[09]|91)|8(?:71|98)","$NP$FG",],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","3(?:1[02-9]|35|49|5|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|[35][2-9]|6[4789]|7\\d|8[23])|5(?:3[03-9]|4[36]|5[02-9]|6[1-46]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]|2[248]|3[04-9]|4[3-6]|6[2368])|8(?:1[236-8]|2[5-7]|3|5[1-9]|7[02-9]|8[3678]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]|4[13]|5[1-5])","$NP$FG",],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","1[3-578]",,],["(10800)(\\d{3})(\\d{4})","$1 $2 $3","108",,]]]',
+"506": '["CR","00",,"(19(?:0[012468]|1[09]|20|66|77|99))",,,"\\d{8,10}","[24-9]\\d{7,9}",[["(\\d{4})(\\d{4})","$1 $2","[24-7]|8[3-9]",,],["(\\d{3})(\\d{3})(\\d{4})","$1-$2-$3","[89]0",,]]]',
+"86": '["CN","(1[1279]\\d{3})?00","0","(1[1279]\\d{3})|0",,,"\\d{4,12}","[1-7]\\d{6,11}|8[0-357-9]\\d{6,9}|9\\d{7,9}",[["(80\\d{2})(\\d{4})","$1 $2","80[2678]","$NP$FG",],["([48]00)(\\d{3})(\\d{4})","$1 $2 $3","[48]00",,],["(\\d{5,6})","$1","100|95",,"NA"],["(\\d{2})(\\d{5,6})","$1 $2","(?:10|2\\d)[19]","$NP$FG",],["(\\d{3})(\\d{5,6})","$1 $2","[3-9]","$NP$FG",],["(\\d{3,4})(\\d{4})","$1 $2","[2-9]",,"NA"],["(21)(\\d{4})(\\d{4,6})","$1 $2 $3","21","$NP$FG",],["([12]\\d)(\\d{4})(\\d{4})","$1 $2 $3","10[1-9]|2[02-9]","$NP$FG",],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","3(?:11|7[179])|4(?:[15]1|3[12])|5(?:1|2[37]|3[12]|51|7[13-79]|9[15])|7(?:31|5[457]|6[09]|91)|8(?:71|98)","$NP$FG",],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","3(?:1[02-9]|35|49|5|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|[35][2-9]|6[4789]|7\\d|8[23])|5(?:3[03-9]|4[36]|5[02-9]|6[1-46]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]|2[248]|3[04-9]|4[3-6]|6[2368])|8(?:1[236-8]|2[5-7]|3|5[1-9]|7[02-9]|8[3678]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]|4[13]|5[1-5])","$NP$FG",],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","1[3-578]",,],["(10800)(\\d{3})(\\d{4})","$1 $2 $3","108",,]]]',
 "257": '["BI","00",,,,,"\\d{8}","[27]\\d{7}",[["([27]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "683": '["NU","00",,,,,"\\d{4}","[1-5]\\d{3}",]',
 "43": '["AT","00","0",,,"$NP$FG","\\d{3,13}","[1-9]\\d{3,12}",[["(1)(\\d{3,12})","$1 $2","1",,],["(5\\d)(\\d{3,5})","$1 $2","5[079]",,],["(5\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","5[079]",,],["(5\\d)(\\d{4})(\\d{4,7})","$1 $2 $3","5[079]",,],["(\\d{3})(\\d{3,10})","$1 $2","316|46|51|732|6(?:44|5[0-3579]|[6-9])|7(?:1|[28]0)|[89]",,],["(\\d{4})(\\d{3,9})","$1 $2","2|3(?:1[1-578]|[3-8])|4[2378]|5[2-6]|6(?:[12]|4[1-35-9]|5[468])|7(?:2[1-8]|35|4[1-8]|[5-79])",,]]]',
-"247": '["AC","00",,,,,"\\d{4}","[2-467]\\d{3}",]',
-"675": '["PG","00",,,,,"\\d{7,8}","[1-9]\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[1-689]",,],["(7\\d{3})(\\d{4})","$1 $2","7",,]]]',
+"247": '["AC","00",,,,,"\\d{4,6}","[2-7]\\d{3,5}",]',
+"675": '["PG","00",,,,,"\\d{7,8}","[1-9]\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[13-689]|27",,],["(\\d{4})(\\d{4})","$1 $2","20|7",,]]]',
 "376": '["AD","00",,,,,"\\d{6,8}","(?:[346-9]|180)\\d{5}",[["(\\d{3})(\\d{3})","$1 $2","[346-9]",,],["(180[02])(\\d{4})","$1 $2","1",,]]]',
 "63": '["PH","00","0",,,,"\\d{5,13}","2\\d{5,7}|[3-9]\\d{7,9}|1800\\d{7,9}",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2","($NP$FG)",],["(2)(\\d{5})","$1 $2","2","($NP$FG)",],["(\\d{4})(\\d{4,6})","$1 $2","3(?:23|39|46)|4(?:2[3-6]|[35]9|4[26]|76)|5(?:22|44)|642|8(?:62|8[245])","($NP$FG)",],["(\\d{5})(\\d{4})","$1 $2","346|4(?:27|9[35])|883","($NP$FG)",],["([3-8]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[3-8]","($NP$FG)",],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","81|9","$NP$FG",],["(1800)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["(1800)(\\d{1,2})(\\d{3})(\\d{4})","$1 $2 $3 $4","1",,]]]',
 "236": '["CF","00",,,,,"\\d{8}","[278]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "590": ['["GP","00","0",,,"$NP$FG","\\d{9}","[56]\\d{8}",[["([56]90)(\\d{2})(\\d{4})","$1 $2-$3",,,]]]','["BL","00","0",,,,"\\d{9}","[56]\\d{8}",]','["MF","00","0",,,,"\\d{9}","[56]\\d{8}",]'],
 "53": '["CU","119","0",,,"($NP$FG)","\\d{4,8}","[2-57]\\d{5,7}",[["(\\d)(\\d{6,7})","$1 $2","7",,],["(\\d{2})(\\d{4,6})","$1 $2","[2-4]",,],["(\\d)(\\d{7})","$1 $2","5","$NP$FG",]]]',
 "64": '["NZ","0(?:0|161)","0",,,"$NP$FG","\\d{7,11}","6[235-9]\\d{6}|[2-57-9]\\d{7,10}",[["([34679])(\\d{3})(\\d{4})","$1-$2 $3","[3467]|9[1-9]",,],["(24099)(\\d{3})","$1 $2","240",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","21",,],["(\\d{2})(\\d{3})(\\d{3,5})","$1 $2 $3","2(?:1[1-9]|[69]|7[0-35-9])|86",,],["(2\\d)(\\d{3,4})(\\d{4})","$1 $2 $3","2[028]",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","2(?:10|74)|5|[89]0",,]]]',
 "965": '["KW","00",,,,,"\\d{7,8}","[12569]\\d{6,7}",[["(\\d{4})(\\d{3,4})","$1 $2","[1269]",,],["(5[015]\\d)(\\d{5})","$1 $2","5",,]]]',
 "224": '["GN","00",,,,,"\\d{8,9}","[367]\\d{7,8}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","3",,],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[67]",,]]]',
@@ -98,29 +98,29 @@ this.PHONE_NUMBER_META_DATA = {
 "386": '["SI","00","0",,,"$NP$FG","\\d{5,8}","[1-7]\\d{6,7}|[89]\\d{4,7}",[["(\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[12]|3[4-8]|4[24-8]|5[2-8]|7[3-8]","($NP$FG)",],["([3-7]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[37][01]|4[0139]|51|6",,],["([89][09])(\\d{3,6})","$1 $2","[89][09]",,],["([58]\\d{2})(\\d{5})","$1 $2","59|8[1-3]",,]]]',
 "679": '["FJ","0(?:0|52)",,,,,"\\d{7}(?:\\d{4})?","[36-9]\\d{6}|0\\d{10}",[["(\\d{3})(\\d{4})","$1 $2","[36-9]",,],["(\\d{4})(\\d{3})(\\d{4})","$1 $2 $3","0",,]]]',
 "238": '["CV","0",,,,,"\\d{7}","[259]\\d{6}",[["(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
 "691": '["FM","00",,,,,"\\d{7}","[39]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
 "262": ['["RE","00","0",,,"$NP$FG","\\d{9}","[268]\\d{8}",[["([268]\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]','["YT","00","0",,,"$NP$FG","\\d{9}","[268]\\d{8}",]'],
 "241": '["GA","00",,,,,"\\d{7,8}","0?\\d{7}",[["(\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[2-7]","0$FG",],["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","0",,]]]',
 "370": '["LT","00","8","[08]",,"($NP-$FG)","\\d{8}","[3-9]\\d{7}",[["([34]\\d)(\\d{6})","$1 $2","37|4(?:1|5[45]|6[2-4])",,],["([3-6]\\d{2})(\\d{5})","$1 $2","3[148]|4(?:[24]|6[09])|528|6",,],["([7-9]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","[7-9]","$NP $FG",],["(5)(2\\d{2})(\\d{4})","$1 $2 $3","52[0-79]",,]]]',
 "256": '["UG","00[057]","0",,,"$NP$FG","\\d{5,9}","\\d{9}",[["(\\d{3})(\\d{6})","$1 $2","[7-9]|20(?:[013-8]|2[5-9])|4(?:6[45]|[7-9])",,],["(\\d{2})(\\d{7})","$1 $2","3|4(?:[1-5]|6[0-36-9])",,],["(2024)(\\d{5})","$1 $2","2024",,]]]',
-"677": '["SB","0[01]",,,,,"\\d{5,7}","[1-9]\\d{4,6}",[["(\\d{3})(\\d{4})","$1 $2","[7-9]",,]]]',
-"377": '["MC","00","0",,,"$NP$FG","\\d{8,9}","[4689]\\d{7,8}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[89]","$FG",],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","4",,],["(6)(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4 $5","6",,]]]',
+"677": '["SB","0[01]",,,,,"\\d{5,7}","[1-9]\\d{4,6}",[["(\\d{2})(\\d{5})","$1 $2","[7-9]",,]]]',
+"377": '["MC","00","0",,,"$NP$FG","\\d{8,9}","[4689]\\d{7,8}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","9","$FG",],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","4",,],["(6)(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4 $5","6",,],["(\\d{3})(\\d{3})(\\d{2})","$1 $2 $3","8","$FG",]]]',
 "382": '["ME","00","0",,,"$NP$FG","\\d{6,9}","[2-9]\\d{7,8}",[["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[2-57-9]|6[3789]",,],["(67)(9)(\\d{3})(\\d{3})","$1 $2 $3 $4","679",,]]]',
 "231": '["LR","00","0",,,"$NP$FG","\\d{7,9}","2\\d{7}|[37-9]\\d{8}|[45]\\d{6}",[["(2\\d)(\\d{3})(\\d{3})","$1 $2 $3","2",,],["([79]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[79]",,],["([4-6])(\\d{3})(\\d{3})","$1 $2 $3","[4-6]",,],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[38]",,]]]',
 "591": '["BO","00(1\\d)?","0","0(1\\d)?",,,"\\d{7,8}","[23467]\\d{7}",[["([234])(\\d{7})","$1 $2","[234]",,],["([67]\\d{7})","$1","[67]",,]]]',
 "808": '["001",,,,,,"\\d{8}","\\d{8}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
 "964": '["IQ","00","0",,,"$NP$FG","\\d{6,10}","[1-7]\\d{7,9}",[["(1)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["([2-6]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[2-6]",,],["(7\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","7",,]]]',
 "225": '["CI","00",,,,,"\\d{8}","[02-7]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "992": '["TJ","810","8",,,"($NP) $FG","\\d{3,9}","[3-59]\\d{8}",[["([349]\\d{2})(\\d{2})(\\d{4})","$1 $2 $3","[34]7|91[78]",,],["([459]\\d)(\\d{3})(\\d{4})","$1 $2 $3","4[48]|5|9(?:1[59]|[0235-9])",,],["(331700)(\\d)(\\d{2})","$1 $2 $3","331",,],["(\\d{4})(\\d)(\\d{4})","$1 $2 $3","3[1-5]",,]]]',
-"55": '["BR","00(?:1[45]|2[135]|31|4[13])","0","(?:0|90)(?:(1[245]|2[135]|[34]1)(\\d{10,11}))?","$2",,"\\d{8,11}","[1-46-9]\\d{7,10}|5\\d{8,9}",[["(\\d{4})(\\d{4})","$1-$2","[2-9](?:[1-9]|0[1-9])","$FG","NA"],["(\\d{5})(\\d{4})","$1-$2","9(?:[1-9]|0[1-9])","$FG","NA"],["(\\d{3,5})","$1","1[125689]","$FG","NA"],["(\\d{2})(\\d{5})(\\d{4})","$1 $2-$3","(?:1[1-9]|2[12478])9","($FG)",],["(\\d{2})(\\d{4})(\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)",],["([34]00\\d)(\\d{4})","$1-$2","[34]00",,],["([3589]00)(\\d{2,3})(\\d{4})","$1 $2 $3","[3589]00","$NP$FG",]]]',
+"55": '["BR","00(?:1[45]|2[135]|31|4[13])","0","(?:0|90)(?:(1[245]|2[135]|[34]1)(\\d{10,11}))?","$2",,"\\d{8,11}","[1-46-9]\\d{7,10}|5\\d{8,9}",[["(\\d{4})(\\d{4})","$1-$2","[2-9](?:[1-9]|0[1-9])","$FG","NA"],["(\\d{5})(\\d{4})","$1-$2","9(?:[1-9]|0[1-9])","$FG","NA"],["(\\d{3,5})","$1","1[125689]","$FG","NA"],["(\\d{2})(\\d{5})(\\d{4})","$1 $2-$3","(?:1[1-9]|2[12478]|9[1-9])9","($FG)",],["(\\d{2})(\\d{4})(\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)",],["([34]00\\d)(\\d{4})","$1-$2","[34]00",,],["([3589]00)(\\d{2,3})(\\d{4})","$1 $2 $3","[3589]00","$NP$FG",]]]',
 "674": '["NR","00",,,,,"\\d{7}","[458]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
 "967": '["YE","00","0",,,"$NP$FG","\\d{6,9}","[1-7]\\d{6,8}",[["([1-7])(\\d{3})(\\d{3,4})","$1 $2 $3","[1-6]|7[24-68]",,],["(7\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","7[0137]",,]]]',
-"49": '["DE","00","0",,,"$NP$FG","\\d{2,15}","[1-35-9]\\d{3,14}|4(?:[0-8]\\d{4,12}|9(?:[0-37]\\d|4(?:[1-35-8]|4\\d?)|5\\d{1,2}|6[1-8]\\d?)\\d{2,7})",[["(1\\d{2})(\\d{7,8})","$1 $2","1[67]",,],["(1\\d{3})(\\d{7})","$1 $2","15",,],["(\\d{2})(\\d{3,11})","$1 $2","3[02]|40|[68]9",,],["(\\d{3})(\\d{3,11})","$1 $2","2(?:\\d1|0[2389]|1[24]|28|34)|3(?:[3-9][15]|40)|[4-8][1-9]1|9(?:06|[1-9]1)",,],["(\\d{4})(\\d{2,11})","$1 $2","[24-6]|[7-9](?:\\d[1-9]|[1-9]\\d)|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])",,],["(3\\d{4})(\\d{1,10})","$1 $2","3",,],["(800)(\\d{7,12})","$1 $2","800",,],["(177)(99)(\\d{7,8})","$1 $2 $3","177",,],["(\\d{3})(\\d)(\\d{4,10})","$1 $2 $3","(?:18|90)0|137",,],["(1\\d{2})(\\d{5,11})","$1 $2","181",,],["(18\\d{3})(\\d{6})","$1 $2","185",,],["(18\\d{2})(\\d{7})","$1 $2","18[68]",,],["(18\\d)(\\d{8})","$1 $2","18[2-579]",,],["(700)(\\d{4})(\\d{4})","$1 $2 $3","700",,],["(138)(\\d{4})","$1 $2","138",,]]]',
+"49": '["DE","00","0",,,"$NP$FG","\\d{2,15}","[1-35-9]\\d{3,14}|4(?:[0-8]\\d{4,12}|9(?:[0-37]\\d|4(?:[1-35-8]|4\\d?)|5\\d{1,2}|6[1-8]\\d?)\\d{2,8})",[["(1\\d{2})(\\d{7,8})","$1 $2","1[67]",,],["(1\\d{3})(\\d{7})","$1 $2","15",,],["(\\d{2})(\\d{3,11})","$1 $2","3[02]|40|[68]9",,],["(\\d{3})(\\d{3,11})","$1 $2","2(?:\\d1|0[2389]|1[24]|28|34)|3(?:[3-9][15]|40)|[4-8][1-9]1|9(?:06|[1-9]1)",,],["(\\d{4})(\\d{2,11})","$1 $2","[24-6]|[7-9](?:\\d[1-9]|[1-9]\\d)|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])",,],["(3\\d{4})(\\d{1,10})","$1 $2","3",,],["(800)(\\d{7,12})","$1 $2","800",,],["(177)(99)(\\d{7,8})","$1 $2 $3","177",,],["(\\d{3})(\\d)(\\d{4,10})","$1 $2 $3","(?:18|90)0|137",,],["(1\\d{2})(\\d{5,11})","$1 $2","181",,],["(18\\d{3})(\\d{6})","$1 $2","185",,],["(18\\d{2})(\\d{7})","$1 $2","18[68]",,],["(18\\d)(\\d{8})","$1 $2","18[2-579]",,],["(700)(\\d{4})(\\d{4})","$1 $2 $3","700",,],["(138)(\\d{4})","$1 $2","138",,]]]',
 "31": '["NL","00","0",,,"$NP$FG","\\d{5,10}","1\\d{4,8}|[2-7]\\d{8}|[89]\\d{6,9}",[["([1-578]\\d)(\\d{3})(\\d{4})","$1 $2 $3","1[035]|2[0346]|3[03568]|4[0356]|5[0358]|7|8[4578]",,],["([1-5]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","1[16-8]|2[259]|3[124]|4[17-9]|5[124679]",,],["(6)(\\d{8})","$1 $2","6[0-57-9]",,],["(66)(\\d{7})","$1 $2","66",,],["(14)(\\d{3,4})","$1 $2","14","$FG",],["([89]0\\d)(\\d{4,7})","$1 $2","80|9",,]]]',
 "970": '["PS","00","0",,,"$NP$FG","\\d{4,10}","[24589]\\d{7,8}|1(?:[78]\\d{8}|[49]\\d{2,3})",[["([2489])(2\\d{2})(\\d{4})","$1 $2 $3","[2489]",,],["(5[69]\\d)(\\d{3})(\\d{3})","$1 $2 $3","5",,],["(1[78]00)(\\d{3})(\\d{3})","$1 $2 $3","1[78]","$FG",]]]',
 "58": '["VE","00","0",,,"$NP$FG","\\d{7,10}","[24589]\\d{9}",[["(\\d{3})(\\d{7})","$1-$2",,,]]]',
 "856": '["LA","00","0",,,"$NP$FG","\\d{6,10}","[2-8]\\d{7,9}",[["(20)(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3 $4","20",,],["([2-8]\\d)(\\d{3})(\\d{3})","$1 $2 $3","2[13]|3[14]|[4-8]",,],["(30)(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3 $4","30",,]]]',
 "354": '["IS","00",,,,,"\\d{7,9}","[4-9]\\d{6}|38\\d{7}",[["(\\d{3})(\\d{4})","$1 $2","[4-9]",,],["(3\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","3",,]]]',
 "242": '["CG","00",,,,,"\\d{9}","[028]\\d{8}",[["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[02]",,],["(\\d)(\\d{4})(\\d{4})","$1 $2 $3","8",,]]]',
 "423": '["LI","00","0",,,,"\\d{7,9}","6\\d{8}|[23789]\\d{6}",[["(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3","[23]|7[3-57-9]|87",,],["(6\\d)(\\d{3})(\\d{3})","$1 $2 $3","6",,],["(6[567]\\d)(\\d{3})(\\d{3})","$1 $2 $3","6[567]",,],["(69)(7\\d{2})(\\d{4})","$1 $2 $3","697",,],["([7-9]0\\d)(\\d{2})(\\d{2})","$1 $2 $3","[7-9]0",,],["([89]0\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[89]0","$NP$FG",]]]',
 "213": '["DZ","00","0",,,"$NP$FG","\\d{8,9}","(?:[1-4]|[5-9]\\d)\\d{7}",[["([1-4]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[1-4]",,],["([5-8]\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[5-8]",,],["(9\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","9",,]]]',
@@ -139,84 +139,84 @@ this.PHONE_NUMBER_META_DATA = {
 "592": '["GY","001",,,,,"\\d{7}","[2-4679]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
 "41": '["CH","00","0",,,"$NP$FG","\\d{9}(?:\\d{3})?","[2-9]\\d{8}|860\\d{9}",[["([2-9]\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[2-7]|[89]1",,],["([89]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","8[047]|90",,],["(\\d{3})(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4 $5","860",,]]]',
 "39": '["IT","00",,,,,"\\d{6,11}","[01589]\\d{5,10}|3(?:[12457-9]\\d{8}|[36]\\d{7,9})",[["(\\d{2})(\\d{3,4})(\\d{4})","$1 $2 $3","0[26]|55",,],["(0[26])(\\d{4})(\\d{5})","$1 $2 $3","0[26]",,],["(0[26])(\\d{4,6})","$1 $2","0[26]",,],["(0\\d{2})(\\d{3,4})(\\d{4})","$1 $2 $3","0[13-57-9][0159]",,],["(\\d{3})(\\d{3,6})","$1 $2","0[13-57-9][0159]|8(?:03|4[17]|9[245])",,],["(0\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","0[13-57-9][2-46-8]",,],["(0\\d{3})(\\d{2,6})","$1 $2","0[13-57-9][2-46-8]",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[13]|8(?:00|4[08]|9[59])",,],["(\\d{4})(\\d{4})","$1 $2","894",,],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","3",,]]]',
 "993": '["TM","810","8",,,"($NP $FG)","\\d{8}","[1-6]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2-$3-$4","12",,],["(\\d{2})(\\d{6})","$1 $2","6","$NP $FG",],["(\\d{3})(\\d)(\\d{2})(\\d{2})","$1 $2-$3-$4","13|[2-5]",,]]]',
 "888": '["001",,,,,,"\\d{11}","\\d{11}",[["(\\d{3})(\\d{3})(\\d{5})","$1 $2 $3",,,]]]',
 "353": '["IE","00","0",,,"($NP$FG)","\\d{5,10}","[124-9]\\d{6,9}",[["(1)(\\d{3,4})(\\d{4})","$1 $2 $3","1",,],["(\\d{2})(\\d{5})","$1 $2","2[24-9]|47|58|6[237-9]|9[35-9]",,],["(\\d{3})(\\d{5})","$1 $2","40[24]|50[45]",,],["(48)(\\d{4})(\\d{4})","$1 $2 $3","48",,],["(818)(\\d{3})(\\d{3})","$1 $2 $3","81",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","[24-69]|7[14]",,],["([78]\\d)(\\d{3,4})(\\d{4})","$1 $2 $3","76|8[35-9]","$NP$FG",],["(700)(\\d{3})(\\d{3})","$1 $2 $3","70","$NP$FG",],["(\\d{4})(\\d{3})(\\d{3})","$1 $2 $3","1(?:8[059]|5)","$FG",]]]',
 "966": '["SA","00","0",,,"$NP$FG","\\d{7,10}","1\\d{7,8}|(?:[2-467]|92)\\d{7}|5\\d{8}|8\\d{9}",[["([1-467])(\\d{3})(\\d{4})","$1 $2 $3","[1-467]",,],["(1\\d)(\\d{3})(\\d{4})","$1 $2 $3","1[1-467]",,],["(5\\d)(\\d{3})(\\d{4})","$1 $2 $3","5",,],["(92\\d{2})(\\d{5})","$1 $2","92","$FG",],["(800)(\\d{3})(\\d{4})","$1 $2 $3","80","$FG",],["(811)(\\d{3})(\\d{3,4})","$1 $2 $3","81",,]]]',
 "380": '["UA","00","0",,,"$NP$FG","\\d{5,9}","[3-689]\\d{8}",[["([3-689]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[38]9|4(?:[45][0-5]|87)|5(?:0|6[37]|7[37])|6[36-8]|9[1-9]",,],["([3-689]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","3[1-8]2|4[13678]2|5(?:[12457]2|6[24])|6(?:[49]2|[12][29]|5[24])|8[0-8]|90",,],["([3-6]\\d{3})(\\d{5})","$1 $2","3(?:5[013-9]|[1-46-8])|4(?:[137][013-9]|6|[45][6-9]|8[4-6])|5(?:[1245][013-9]|6[0135-9]|3|7[4-6])|6(?:[49][013-9]|5[0135-9]|[12][13-8])",,]]]',
-"98": '["IR","00","0",,,"$NP$FG","\\d{4,10}","[14-8]\\d{6,9}|[23]\\d{4,9}|9(?:[1-4]\\d{8}|9\\d{2,8})",[["(2[15])(\\d{3,5})","$1 $2","2(?:1|5[0-47-9])",,],["(2[15])(\\d{3})(\\d{3,4})","$1 $2 $3","2(?:1|5[0-47-9])",,],["(2\\d)(\\d{4})(\\d{4})","$1 $2 $3","2(?:[16]|5[0-47-9])",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[13-9]|2[02-57-9]",,],["(\\d{3})(\\d{2})(\\d{2,3})","$1 $2 $3","[13-9]|2[02-57-9]",,],["(\\d{3})(\\d{3})","$1 $2","[13-9]|2[02-57-9]",,]]]',
+"98": '["IR","00","0",,,"$NP$FG","\\d{4,10}","[14-8]\\d{6,9}|[23]\\d{4,9}|9(?:[0-4]\\d{8}|9\\d{2,8})",[["(21)(\\d{3,5})","$1 $2","21",,],["(2[15])(\\d{3})(\\d{3,4})","$1 $2 $3","2(?:1|5[0-47-9])",,],["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","2[156]|31|51|71|86",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[13-9]|2[02-47-9]",,],["(\\d{3})(\\d{2})(\\d{2,3})","$1 $2 $3","[13-9]|2[02-47-9]",,],["(\\d{3})(\\d{3})","$1 $2","[13-9]|2[02-47-9]",,]]]',
 "971": '["AE","00","0",,,"$NP$FG","\\d{5,12}","[2-79]\\d{7,8}|800\\d{2,9}",[["([2-4679])(\\d{3})(\\d{4})","$1 $2 $3","[2-4679][2-8]",,],["(5[0256])(\\d{3})(\\d{4})","$1 $2 $3","5",,],["([479]00)(\\d)(\\d{5})","$1 $2 $3","[479]0","$FG",],["([68]00)(\\d{2,9})","$1 $2","60|8","$FG",]]]',
 "30": '["GR","00",,,,,"\\d{10}","[26-9]\\d{9}",[["([27]\\d)(\\d{4})(\\d{4})","$1 $2 $3","21|7",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","2[2-9]1|[689]",,],["(2\\d{3})(\\d{6})","$1 $2","2[2-9][02-9]",,]]]',
 "228": '["TG","00",,,,,"\\d{8}","[29]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "48": '["PL","00",,,,,"\\d{6,9}","[12]\\d{6,8}|[3-57-9]\\d{8}|6\\d{5,8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[124]|3[2-4]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145]",,],["(\\d{2})(\\d{1})(\\d{4})","$1 $2 $3","[12]2",,],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","39|5[0137]|6[0469]|7[02389]|8[08]",,],["(\\d{3})(\\d{2})(\\d{2,3})","$1 $2 $3","64",,],["(\\d{3})(\\d{3})","$1 $2","64",,]]]',
 "886": '["TW","0(?:0[25679]|19)","0",,,"$NP$FG","\\d{8,10}","[2-689]\\d{7,8}|7\\d{7,9}",[["([2-8])(\\d{3,4})(\\d{4})","$1 $2 $3","[2-6]|[78][1-9]",,],["([89]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","80|9",,],["(70)(\\d{4})(\\d{4})","$1 $2 $3","70",,]]]',
 "212": ['["MA","00","0",,,"$NP$FG","\\d{9}","[5689]\\d{8}",[["([56]\\d{2})(\\d{6})","$1-$2","5(?:2[015-7]|3[0-4])|6",,],["([58]\\d{3})(\\d{5})","$1-$2","5(?:2[2-489]|3[5-9])|892",,],["(5\\d{4})(\\d{4})","$1-$2","5(?:29|38)",,],["(8[09])(\\d{7})","$1-$2","8(?:0|9[013-9])",,]]]','["EH","00","0",,,"$NP$FG","\\d{9}","[5689]\\d{8}",]'],
 "372": '["EE","00",,,,,"\\d{4,10}","1\\d{3,4}|[3-9]\\d{6,7}|800\\d{6,7}",[["([3-79]\\d{2})(\\d{4})","$1 $2","[369]|4[3-8]|5(?:[0-2]|5[0-478]|6[45])|7[1-9]",,],["(70)(\\d{2})(\\d{4})","$1 $2 $3","70",,],["(8000)(\\d{3})(\\d{3})","$1 $2 $3","800",,],["([458]\\d{3})(\\d{3,4})","$1 $2","40|5|8(?:00|[1-5])",,]]]',
 "598": '["UY","0(?:1[3-9]\\d|0)","0",,,,"\\d{7,8}","[2489]\\d{6,7}",[["(\\d{4})(\\d{4})","$1 $2","[24]",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","9[1-9]","$NP$FG",],["(\\d{3})(\\d{4})","$1 $2","[89]0","$NP$FG",]]]',
 "502": '["GT","00",,,,,"\\d{8}(?:\\d{3})?","[2-7]\\d{7}|1[89]\\d{9}",[["(\\d{4})(\\d{4})","$1 $2","[2-7]",,],["(\\d{4})(\\d{3})(\\d{4})","$1 $2 $3","1",,]]]',
 "82": '["KR","00(?:[124-68]|[37]\\d{2})","0","0(8[1-46-8]|85\\d{2})?",,"$NP$FG","\\d{4,10}","[1-7]\\d{3,9}|8\\d{8}",[["(\\d{2})(\\d{4})(\\d{4})","$1-$2-$3","1(?:0|1[19]|[69]9|5[458])|[57]0",,],["(\\d{2})(\\d{3,4})(\\d{4})","$1-$2-$3","1(?:[169][2-8]|[78]|5[1-4])|[68]0|[3-6][1-9][1-9]",,],["(\\d{3})(\\d)(\\d{4})","$1-$2-$3","131",,],["(\\d{3})(\\d{2})(\\d{4})","$1-$2-$3","131",,],["(\\d{3})(\\d{3})(\\d{4})","$1-$2-$3","13[2-9]",,],["(\\d{2})(\\d{2})(\\d{3})(\\d{4})","$1-$2-$3-$4","30",,],["(\\d)(\\d{3,4})(\\d{4})","$1-$2-$3","2[1-9]",,],["(\\d)(\\d{3,4})","$1-$2","21[0-46-9]",,],["(\\d{2})(\\d{3,4})","$1-$2","[3-6][1-9]1",,],["(\\d{4})(\\d{4})","$1-$2","1(?:5[46-9]|6[04678])","$FG",]]]',
 "253": '["DJ","00",,,,,"\\d{8}","[27]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
-"91": '["IN","00","0",,,"$NP$FG","\\d{6,13}","1\\d{7,12}|[2-9]\\d{9,10}",[["(\\d{2})(\\d{2})(\\d{6})","$1 $2 $3","7(?:0[2-4]|2[0579]|3[057-9]|4[0-389]|6[0-35-9]|[57]|8[0-79])|8(?:0[015689]|1[0-57-9]|2[2356-9]|3[0-57-9]|[45]|6[02457-9]|7[1-69]|8[0124-9]|9[02-9])|9",,],["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","11|2[02]|33|4[04]|79|80[2-46]",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1(?:2[0-249]|3[0-25]|4[145]|[569][14]|7[1257]|8[1346]|[68][1-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[126-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:[136][25]|22|4[28]|5[12]|[78]1|9[15])|6(?:12|[2345]1|57|6[13]|7[14]|80)",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1|88)",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91)",,],["(\\d{4})(\\d{3})(\\d{3})","$1 $2 $3","1(?:[23579]|[468][1-9])|[2-8]",,],["(1600)(\\d{2})(\\d{4})","$1 $2 $3","160","$FG",],["(1800)(\\d{4,5})","$1 $2","180","$FG",],["(18[06]0)(\\d{2,4})(\\d{4})","$1 $2 $3","18[06]","$FG",],["(140)(\\d{3})(\\d{4})","$1 $2 $3","140","$FG",],["(\\d{4})(\\d{3})(\\d{4})(\\d{2})","$1 $2 $3 $4","18[06]","$FG",]]]',
+"91": '["IN","00","0",,,"$NP$FG","\\d{6,13}","1\\d{7,12}|[2-9]\\d{9,10}",[["(\\d{5})(\\d{5})","$1 $2","7(?:0[2-9]|2[0579]|3[057-9]|4[0-389]|6[0-35-9]|[57]|8[0-79])|8(?:0[015689]|1[0-57-9]|2[2356-9]|3[0-57-9]|[45]|6[02457-9]|7[1-69]|8[0124-9]|9[02-9])|9",,],["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","11|2[02]|33|4[04]|79|80[2-46]",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1(?:2[0-249]|3[0-25]|4[145]|[569][14]|7[1257]|8[1346]|[68][1-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[126-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:[136][25]|22|4[28]|5[12]|[78]1|9[15])|6(?:12|[2345]1|57|6[13]|7[14]|80)",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1|88)",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91)",,],["(\\d{4})(\\d{3})(\\d{3})","$1 $2 $3","1(?:[23579]|[468][1-9])|[2-8]",,],["(1600)(\\d{2})(\\d{4})","$1 $2 $3","160","$FG",],["(1800)(\\d{4,5})","$1 $2","180","$FG",],["(18[06]0)(\\d{2,4})(\\d{4})","$1 $2 $3","18[06]","$FG",],["(140)(\\d{3})(\\d{4})","$1 $2 $3","140","$FG",],["(\\d{4})(\\d{3})(\\d{4})(\\d{2})","$1 $2 $3 $4","18[06]","$FG",]]]',
 "389": '["MK","00","0",,,"$NP$FG","\\d{8}","[2-578]\\d{7}",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2",,],["([347]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[347]",,],["([58]\\d{2})(\\d)(\\d{2})(\\d{2})","$1 $2 $3 $4","[58]",,]]]',
 "1": ['["US","011","1",,,,"\\d{7}(?:\\d{3})?","[2-9]\\d{9}",[["(\\d{3})(\\d{4})","$1-$2",,,"NA"],["(\\d{3})(\\d{3})(\\d{4})","($1) $2-$3",,,"$1-$2-$3"]]]','["AI","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["AS","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["BB","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["BM","011","1",,,,"\\d{7}(?:\\d{3})?","[4589]\\d{9}",]','["BS","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["CA","011","1",,,,"\\d{7}(?:\\d{3})?","[2-9]\\d{9}|3\\d{6}",]','["DM","011","1",,,,"\\d{7}(?:\\d{3})?","[57-9]\\d{9}",]','["DO","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["GD","011","1",,,,"\\d{7}(?:\\d{3})?","[4589]\\d{9}",]','["GU","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["JM","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["KN","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["KY","011","1",,,,"\\d{7}(?:\\d{3})?","[3589]\\d{9}",]','["LC","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["MP","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["MS","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["PR","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["SX","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["TC","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["TT","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["AG","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["VC","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["VG","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["VI","011","1",,,,"\\d{7}(?:\\d{3})?","[3589]\\d{9}",]'],
 "60": '["MY","00","0",,,,"\\d{6,10}","[13-9]\\d{7,9}",[["([4-79])(\\d{3})(\\d{4})","$1-$2 $3","[4-79]","$NP$FG",],["(3)(\\d{4})(\\d{4})","$1-$2 $3","3","$NP$FG",],["([18]\\d)(\\d{3})(\\d{3,4})","$1-$2 $3","1[02-46-9][1-9]|8","$NP$FG",],["(1)([36-8]00)(\\d{2})(\\d{4})","$1-$2-$3-$4","1[36-8]0",,],["(11)(\\d{4})(\\d{4})","$1-$2 $3","11","$NP$FG",],["(15[49])(\\d{3})(\\d{4})","$1-$2 $3","15","$NP$FG",]]]',
 "355": '["AL","00","0",,,"$NP$FG","\\d{5,9}","[2-57]\\d{7}|6\\d{8}|8\\d{5,7}|9\\d{5}",[["(4)(\\d{3})(\\d{4})","$1 $2 $3","4[0-6]",,],["(6[6-9])(\\d{3})(\\d{4})","$1 $2 $3","6",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[2358][2-5]|4[7-9]",,],["(\\d{3})(\\d{3,5})","$1 $2","[235][16-9]|8[016-9]|[79]",,]]]',
-"254": '["KE","000","0",,,"$NP$FG","\\d{5,10}","20\\d{6,7}|[4-9]\\d{6,9}",[["(\\d{2})(\\d{4,7})","$1 $2","[24-6]",,],["(\\d{3})(\\d{6,7})","$1 $2","7",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[89]",,]]]',
+"254": '["KE","000","0",,,"$NP$FG","\\d{7,10}","20\\d{6,7}|[4-9]\\d{6,9}",[["(\\d{2})(\\d{5,7})","$1 $2","[24-6]",,],["(\\d{3})(\\d{6,7})","$1 $2","7",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[89]",,]]]',
 "223": '["ML","00",,,,,"\\d{8}","[246-9]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[246-9]",,],["(\\d{4})","$1","67|74",,"NA"]]]',
 "686": '["KI","00",,"0",,,"\\d{5,8}","[2458]\\d{4}|3\\d{4,7}|7\\d{7}",]',
 "994": '["AZ","00","0",,,"($NP$FG)","\\d{7,9}","[1-9]\\d{8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","(?:1[28]|2(?:[45]2|[0-36])|365)",,],["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[4-8]","$NP$FG",],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","9","$NP$FG",]]]',
 "979": '["001",,,,,,"\\d{9}","\\d{9}",[["(\\d)(\\d{4})(\\d{4})","$1 $2 $3",,,]]]',
 "66": '["TH","00","0",,,"$NP$FG","\\d{4}|\\d{8,10}","[2-9]\\d{7,8}|1\\d{3}(?:\\d{6})?",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2",,],["([3-9]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[3-9]",,],["(1[89]00)(\\d{3})(\\d{3})","$1 $2 $3","1","$FG",]]]',
 "233": '["GH","00","0",,,"$NP$FG","\\d{7,9}","[235]\\d{8}|8\\d{7}",[["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[235]",,],["(\\d{3})(\\d{5})","$1 $2","8",,]]]',
 "593": '["EC","00","0",,,"($NP$FG)","\\d{7,11}","1\\d{9,10}|[2-8]\\d{7}|9\\d{8}",[["(\\d)(\\d{3})(\\d{4})","$1 $2-$3","[247]|[356][2-8]",,"$1-$2-$3"],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","9","$NP$FG",],["(1800)(\\d{3})(\\d{3,4})","$1 $2 $3","1","$FG",]]]',
 "509": '["HT","00",,,,,"\\d{8}","[2-489]\\d{7}",[["(\\d{2})(\\d{2})(\\d{4})","$1 $2 $3",,,]]]',
-"54": '["AR","00","0","0?(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:02?|1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[67])|4(?:7[3-578]|9)|6(?:[0136]|2[24-6]|4[6-8]?|5[15-8])|80|9(?:0[1-3]|[19]|2\\d|3[1-6]|4[02568]?|5[2-4]|6[2-46]|72?|8[23]?))|3(?:3(?:2[79]|6|8[2578])|4(?:0[124-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6[02-9]|7[126]|8[2379]?|9[1-36-8])|5(?:1|2[1245]|3[237]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|6[24]|7(?:1[1568]|2[15]|3[145]|4[13]|5[14-8]|[069]|7[2-57]|8[126])|8(?:[01]|2[15-7]|3[2578]?|4[13-6]|5[4-8]?|6[1-357-9]|7[36-8]?|8[5-8]?|9[124])))15)?","9$1","$NP$FG","\\d{6,11}","[1-368]\\d{9}|9\\d{10}",[["([68]\\d{2})(\\d{3})(\\d{4})","$1-$2-$3","[68]",,],["(9)(11)(\\d{4})(\\d{4})","$2 15-$3-$4","911",,"$1 $2 $3-$4"],["(9)(\\d{3})(\\d{3})(\\d{4})","$2 15-$3-$4","9(?:2[234689]|3[3-8])",,"$1 $2 $3-$4"],["(9)(\\d{4})(\\d{3})(\\d{3})","$2 15-$3-$4","93[58]",,"$1 $2 $3-$4"],["(9)(\\d{4})(\\d{2})(\\d{4})","$2 15-$3-$4","9[23]",,"$1 $2 $3-$4"],["(11)(\\d{4})(\\d{4})","$1 $2-$3","1",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2-$3","2(?:2[013]|3[067]|49|6[01346]|80|9[147-9])|3(?:36|4[12358]|5[138]|6[24]|7[069]|8[013578])",,],["(\\d{4})(\\d{3})(\\d{3})","$1 $2-$3","3(?:53|8[78])",,],["(\\d{4})(\\d{2})(\\d{4})","$1 $2-$3","[23]",,],["(\\d{3})","$1","1[012]|911","$FG","NA"]]]',
+"54": '["AR","00","0","0?(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:02?|1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[67])|4(?:7[3-578]|9)|6(?:[0136]|2[24-6]|4[6-8]?|5[15-8])|80|9(?:0[1-3]|[19]|2\\d|3[1-6]|4[02568]?|5[2-4]|6[2-46]|72?|8[23]?))|3(?:3(?:2[79]|6|8[2578])|4(?:0[124-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6[02-9]|7[126]|8[2379]?|9[1-36-8])|5(?:1|2[1245]|3[237]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|6[24]|7(?:1[1568]|2[15]|3[145]|4[13]|5[14-8]|[069]|7[2-57]|8[126])|8(?:[01]|2[15-7]|3[2578]?|4[13-6]|5[4-8]?|6[1-357-9]|7[36-8]?|8[5-8]?|9[124])))?15)?","9$1","$NP$FG","\\d{6,11}","11\\d{8}|[2368]\\d{9}|9\\d{10}",[["([68]\\d{2})(\\d{3})(\\d{4})","$1-$2-$3","[68]",,],["(\\d{2})(\\d{4})","$1-$2","[2-9]","$FG","NA"],["(\\d{3})(\\d{4})","$1-$2","[2-9]","$FG","NA"],["(\\d{4})(\\d{4})","$1-$2","[2-9]","$FG","NA"],["(9)(11)(\\d{4})(\\d{4})","$2 15-$3-$4","911",,"$1 $2 $3-$4"],["(9)(\\d{3})(\\d{3})(\\d{4})","$2 15-$3-$4","9(?:2[234689]|3[3-8])",,"$1 $2 $3-$4"],["(9)(\\d{4})(\\d{2})(\\d{4})","$2 15-$3-$4","9[23]",,"$1 $2 $3-$4"],["(11)(\\d{4})(\\d{4})","$1 $2-$3","1",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2-$3","2(?:2[013]|3[067]|49|6[01346]|80|9[147-9])|3(?:36|4[12358]|5[138]|6[24]|7[069]|8[013578])",,],["(\\d{4})(\\d{2})(\\d{4})","$1 $2-$3","[23]",,],["(\\d{3})","$1","1[012]|911","$FG","NA"]]]',
 "57": '["CO","00(?:4(?:[14]4|56)|[579])","0","0([3579]|4(?:44|56))?",,,"\\d{7,11}","(?:[13]\\d{0,3}|[24-8])\\d{7}",[["(\\d)(\\d{7})","$1 $2","1(?:8[2-9]|9[0-3]|[2-7])|[24-8]","($FG)",],["(\\d{3})(\\d{7})","$1 $2","3",,],["(1)(\\d{3})(\\d{7})","$1-$2-$3","1(?:80|9[04])","$NP$FG","$1 $2 $3"]]]',
-"597": '["SR","00",,,,,"\\d{6,7}","[2-8]\\d{5,6}",[["(\\d{3})(\\d{3})","$1-$2","[2-4]|5[2-58]",,],["(\\d{2})(\\d{2})(\\d{2})","$1-$2-$3","56",,],["(\\d{3})(\\d{4})","$1-$2","[6-8]",,]]]',
+"597": '["SR","00",,,,,"\\d{6,7}","[2-8]\\d{5,6}",[["(\\d{3})(\\d{3})","$1-$2","[2-4]|5[2-58]",,],["(\\d{2})(\\d{2})(\\d{2})","$1-$2-$3","56",,],["(\\d{3})(\\d{4})","$1-$2","59|[6-8]",,]]]',
 "676": '["TO","00",,,,,"\\d{5,7}","[02-8]\\d{4,6}",[["(\\d{2})(\\d{3})","$1-$2","[1-6]|7[0-4]|8[05]",,],["(\\d{3})(\\d{4})","$1 $2","7[5-9]|8[7-9]",,],["(\\d{4})(\\d{3})","$1 $2","0",,]]]',
 "505": '["NI","00",,,,,"\\d{8}","[12578]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
 "850": '["KP","00|99","0",,,"$NP$FG","\\d{6,8}|\\d{10}","1\\d{9}|[28]\\d{7}",[["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1",,],["(\\d)(\\d{3})(\\d{4})","$1 $2 $3","2",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","8",,]]]',
 "7": ['["RU","810","8",,,"$NP ($FG)","\\d{10}","[3489]\\d{9}",[["(\\d{3})(\\d{2})(\\d{2})","$1-$2-$3","[1-79]","$FG","NA"],["([3489]\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2-$3-$4","[34689]",,],["(7\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","7",,]]]','["KZ","810","8",,,,"\\d{10}","(?:33\\d|7\\d{2}|80[09])\\d{7}",]'],
 "268": '["SZ","00",,,,,"\\d{8}","[027]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2","[027]",,]]]',
 "501": '["BZ","00",,,,,"\\d{7}(?:\\d{4})?","[2-8]\\d{6}|0\\d{10}",[["(\\d{3})(\\d{4})","$1-$2","[2-8]",,],["(0)(800)(\\d{4})(\\d{3})","$1-$2-$3-$4","0",,]]]',
-"252": '["SO","00","0",,,,"\\d{7,9}","[1-79]\\d{6,8}",[["(\\d)(\\d{6})","$1 $2","2[0-79]|[13-5]",,],["(\\d)(\\d{7})","$1 $2","24|[67]",,],["(\\d{2})(\\d{5,7})","$1 $2","15|28|6[1378]|9",,],["(69\\d)(\\d{6})","$1 $2","69",,]]]',
+"252": '["SO","00","0",,,,"\\d{7,9}","[1-79]\\d{6,8}",[["(\\d)(\\d{6})","$1 $2","2[0-79]|[13-5]",,],["(\\d)(\\d{7})","$1 $2","24|[67]",,],["(\\d{2})(\\d{5,7})","$1 $2","15|28|6[1378]",,],["(69\\d)(\\d{6})","$1 $2","69",,],["(90\\d)(\\d{3})(\\d{3})","$1 $2 $3","90",,]]]',
 "229": '["BJ","00",,,,,"\\d{4,8}","[2689]\\d{7}|7\\d{3}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "680": '["PW","01[12]",,,,,"\\d{7}","[2-8]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
 "263": '["ZW","00","0",,,"$NP$FG","\\d{3,10}","2(?:[012457-9]\\d{3,8}|6\\d{3,6})|[13-79]\\d{4,8}|8[06]\\d{8}",[["([49])(\\d{3})(\\d{2,5})","$1 $2 $3","4|9[2-9]",,],["([179]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[19]1|7",,],["(86\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","86[24]",,],["([2356]\\d{2})(\\d{3,5})","$1 $2","2(?:[278]|0[45]|[49]8)|3(?:08|17|3[78]|[78])|5[15][78]|6(?:[29]8|37|[68][78])",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","2(?:[278]|0[45]|48)|3(?:08|17|3[78]|[78])|5[15][78]|6(?:[29]8|37|[68][78])|80",,],["([1-356]\\d)(\\d{3,5})","$1 $2","1[3-9]|2(?:[1-469]|0[0-35-9]|[45][0-79])|3(?:0[0-79]|1[0-689]|[24-69]|3[0-69])|5(?:[02-46-9]|[15][0-69])|6(?:[0145]|[29][0-79]|3[0-689]|[68][0-69])",,],["([1-356]\\d)(\\d{3})(\\d{3})","$1 $2 $3","1[3-9]|2(?:[1-469]|0[0-35-9]|[45][0-79])|3(?:0[0-79]|1[0-689]|[24-69]|3[0-69])|5(?:[02-46-9]|[15][0-69])|6(?:[0145]|[29][0-79]|3[0-689]|[68][0-69])",,],["([25]\\d{3})(\\d{3,5})","$1 $2","(?:25|54)8",,],["([25]\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","(?:25|54)8",,],["(8\\d{3})(\\d{6})","$1 $2","86",,]]]',
 "90": '["TR","00","0",,,,"\\d{7,10}","[2-589]\\d{9}|444\\d{4}",[["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","[23]|4(?:[0-35-9]|4[0-35-9])","($NP$FG)",],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","[589]","$NP$FG",],["(444)(\\d{1})(\\d{3})","$1 $2 $3","444",,]]]',
 "352": '["LU","00",,"(15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\\d)",,,"\\d{4,11}","[24-9]\\d{3,10}|3(?:[0-46-9]\\d{2,9}|5[013-9]\\d{1,8})",[["(\\d{2})(\\d{3})","$1 $2","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",,],["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",,],["(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","20",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{1,2})","$1 $2 $3 $4","2(?:[0367]|4[3-8])",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3 $4","20",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{1,2})","$1 $2 $3 $4 $5","2(?:[0367]|4[3-8])",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{1,4})","$1 $2 $3 $4","2(?:[12589]|4[12])|[3-5]|7[1-9]|[89](?:[1-9]|0[2-9])",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","[89]0[01]|70",,],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","6",,]]]',
 "47": ['["NO","00",,,,,"\\d{5}(?:\\d{3})?","0\\d{4}|[2-9]\\d{7}",[["([489]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","[489]",,],["([235-7]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[235-7]",,]]]','["SJ","00",,,,,"\\d{5}(?:\\d{3})?","0\\d{4}|[4789]\\d{7}",]'],
 "243": '["CD","00","0",,,"$NP$FG","\\d{7,9}","[2-6]\\d{6}|[18]\\d{6,8}|9\\d{8}",[["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","12",,],["([89]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","8[0-2459]|9",,],["(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","88",,],["(\\d{2})(\\d{5})","$1 $2","[1-6]",,]]]',
 "220": '["GM","00",,,,,"\\d{7}","[2-9]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
 "687": '["NC","00",,,,,"\\d{6}","[2-57-9]\\d{5}",[["(\\d{2})(\\d{2})(\\d{2})","$1.$2.$3","[2-46-9]|5[0-4]",,]]]',
 "995": '["GE","00","0",,,,"\\d{6,9}","[34578]\\d{8}",[["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[348]","$NP$FG",],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","7","$NP$FG",],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","5","$FG",]]]',
-"961": '["LB","00","0",,,,"\\d{7,8}","[13-9]\\d{6,7}",[["(\\d)(\\d{3})(\\d{3})","$1 $2 $3","[13-6]|7(?:[2-579]|62|8[0-7])|[89][2-9]","$NP$FG",],["([7-9]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[89][01]|7(?:[01]|6[013-9]|8[89]|91)",,]]]',
+"961": '["LB","00","0",,,,"\\d{7,8}","[13-9]\\d{6,7}",[["(\\d)(\\d{3})(\\d{3})","$1 $2 $3","[13-6]|7(?:[2-579]|62|8[0-7])|[89][2-9]","$NP$FG",],["([7-9]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[89][01]|7(?:[019]|6[013-9]|8[89])",,]]]',
 "40": '["RO","00","0",,,"$NP$FG","\\d{6,9}","2\\d{5,8}|[37-9]\\d{8}",[["([237]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[23]1",,],["(21)(\\d{4})","$1 $2","21",,],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[23][3-7]|[7-9]",,],["(2\\d{2})(\\d{3})","$1 $2","2[3-6]",,]]]',
 "232": '["SL","00","0",,,"($NP$FG)","\\d{6,8}","[2-578]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2",,,]]]',
 "594": '["GF","00","0",,,"$NP$FG","\\d{9}","[56]\\d{8}",[["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "976": '["MN","001","0",,,"$NP$FG","\\d{6,10}","[12]\\d{7,9}|[57-9]\\d{7}",[["([12]\\d)(\\d{2})(\\d{4})","$1 $2 $3","[12]1",,],["([12]2\\d)(\\d{5,6})","$1 $2","[12]2[1-3]",,],["([12]\\d{3})(\\d{5})","$1 $2","[12](?:27|[3-5])",,],["(\\d{4})(\\d{4})","$1 $2","[57-9]","$FG",],["([12]\\d{4})(\\d{4,5})","$1 $2","[12](?:27|[3-5])",,]]]',
 "20": '["EG","00","0",,,"$NP$FG","\\d{5,10}","1\\d{4,9}|[2456]\\d{8}|3\\d{7}|[89]\\d{8,9}",[["(\\d)(\\d{7,8})","$1 $2","[23]",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1[012]|[89]00",,],["(\\d{2})(\\d{6,7})","$1 $2","1[35]|[4-6]|[89][2-9]",,]]]',
-"689": '["PF","00",,,,,"\\d{6}(?:\\d{2})?","[2-79]\\d{5}|8\\d{5,7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","89",,],["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
-"56": '["CL","(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0","0","0|(1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))",,"$NP$FG","\\d{6,11}","(?:[2-9]|600|123)\\d{7,8}",[["(\\d)(\\d{4})(\\d{4})","$1 $2 $3","22","($FG)",],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[357]|4[1-35]|6[13-57]","($FG)",],["(\\d{2})(\\d{2})(\\d{4})","$1 $2 $3","65","($FG)",],["(9)([5-9]\\d{3})(\\d{4})","$1 $2 $3","9",,],["(44)(\\d{3})(\\d{4})","$1 $2 $3","44",,],["([68]00)(\\d{3})(\\d{3,4})","$1 $2 $3","60|8","$FG",],["(600)(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3 $4","60","$FG",],["(1230)(\\d{3})(\\d{4})","$1 $2 $3","1","$FG",],["(\\d{5})(\\d{4})","$1 $2","219","($FG)",],["(\\d{4,5})","$1","[1-9]","$FG","NA"]]]',
+"689": '["PF","00",,,,,"\\d{6}(?:\\d{2})?","4\\d{5,7}|8\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","4[09]|8[79]",,],["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3","44",,]]]',
+"56": '["CL","(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0","0","0|(1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))",,"$NP$FG","\\d{7,11}","(?:[2-9]|600|123)\\d{7,8}",[["(\\d)(\\d{4})(\\d{4})","$1 $2 $3","22","($FG)",],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[357]|4[1-35]|6[13-57]","($FG)",],["(9)(\\d{4})(\\d{4})","$1 $2 $3","9",,],["(44)(\\d{3})(\\d{4})","$1 $2 $3","44",,],["([68]00)(\\d{3})(\\d{3,4})","$1 $2 $3","60|8","$FG",],["(600)(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3 $4","60","$FG",],["(1230)(\\d{3})(\\d{4})","$1 $2 $3","1","$FG",],["(\\d{5})(\\d{4})","$1 $2","219","($FG)",],["(\\d{4,5})","$1","[1-9]","$FG","NA"]]]',
 "596": '["MQ","00","0",,,"$NP$FG","\\d{9}","[56]\\d{8}",[["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "508": '["PM","00","0",,,"$NP$FG","\\d{6}","[45]\\d{5}",[["([45]\\d)(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
 "269": '["KM","00",,,,,"\\d{7}","[379]\\d{6}",[["(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
 "358": ['["FI","00|99[049]","0",,,"$NP$FG","\\d{5,12}","1\\d{4,11}|[2-9]\\d{4,10}",[["(\\d{3})(\\d{3,7})","$1 $2","(?:[1-3]00|[6-8]0)",,],["(\\d{2})(\\d{4,10})","$1 $2","[14]|2[09]|50|7[135]",,],["(\\d)(\\d{4,11})","$1 $2","[25689][1-8]|3",,]]]','["AX","00|99[049]","0",,,"$NP$FG","\\d{5,12}","[135]\\d{5,9}|[27]\\d{4,9}|4\\d{5,10}|6\\d{7,8}|8\\d{6,9}",]'],
 "251": '["ET","00","0",,,"$NP$FG","\\d{7,9}","[1-59]\\d{8}",[["([1-59]\\d)(\\d{3})(\\d{4})","$1 $2 $3",,,]]]',
 "681": '["WF","00",,,,,"\\d{6}","[5-7]\\d{5}",[["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
 "853": '["MO","00",,,,,"\\d{8}","[268]\\d{7}",[["([268]\\d{3})(\\d{4})","$1 $2",,,]]]',
 "44": ['["GB","00","0",,,"$NP$FG","\\d{4,10}","\\d{7,10}",[["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","2|5[56]|7(?:0|6[013-9])",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1(?:1|\\d1)|3|9[018]",,],["(\\d{5})(\\d{4,5})","$1 $2","1(?:38|5[23]|69|76|94)",,],["(1\\d{3})(\\d{5,6})","$1 $2","1",,],["(7\\d{3})(\\d{6})","$1 $2","7(?:[1-5789]|62)",,],["(800)(\\d{4})","$1 $2","800",,],["(845)(46)(4\\d)","$1 $2 $3","845",,],["(8\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","8(?:4[2-5]|7[0-3])",,],["(80\\d)(\\d{3})(\\d{4})","$1 $2 $3","80",,],["([58]00)(\\d{6})","$1 $2","[58]00",,]]]','["GG","00","0",,,"$NP$FG","\\d{6,10}","[135789]\\d{6,9}",]','["IM","00","0",,,"$NP$FG","\\d{6,10}","[135789]\\d{6,9}",]','["JE","00","0",,,"$NP$FG","\\d{6,10}","[135789]\\d{6,9}",]'],
 "244": '["AO","00",,,,,"\\d{9}","[29]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
 "211": '["SS","00","0",,,,"\\d{9}","[19]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,"$NP$FG",]]]',
 "373": '["MD","00","0",,,"$NP$FG","\\d{8}","[235-9]\\d{7}",[["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","22|3",,],["([25-7]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","2[13-79]|[5-7]",,],["([89]\\d{2})(\\d{5})","$1 $2","[89]",,]]]',
-"996": '["KG","00","0",,,"$NP$FG","\\d{5,10}","[35-8]\\d{8,9}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","31[25]|[5-7]",,],["(\\d{4})(\\d{5})","$1 $2","3(?:1[36]|[2-9])",,],["(\\d{3})(\\d{3})(\\d)(\\d{3})","$1 $2 $3 $4","8",,]]]',
-"93": '["AF","00","0",,,"$NP$FG","\\d{7,9}","[2-7]\\d{8}",[["([2-7]\\d)(\\d{3})(\\d{4})","$1 $2 $3",,,]]]',
+"996": '["KG","00","0",,,"$NP$FG","\\d{5,10}","[235-8]\\d{8,9}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[25-7]|31[25]",,],["(\\d{4})(\\d{5})","$1 $2","3(?:1[36]|[2-9])",,],["(\\d{3})(\\d{3})(\\d)(\\d{3})","$1 $2 $3 $4","8",,]]]',
+"93": '["AF","00","0",,,"$NP$FG","\\d{7,9}","[2-7]\\d{8}",[["([2-7]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[2-6]|7[013-9]",,],["(729)(\\d{3})(\\d{3})","$1 $2 $3","729",,]]]',
 "260": '["ZM","00","0",,,"$NP$FG","\\d{9}","[289]\\d{8}",[["([29]\\d)(\\d{7})","$1 $2","[29]",,],["(800)(\\d{3})(\\d{3})","$1 $2 $3","8",,]]]',
 "378": '["SM","00",,"(?:0549)?([89]\\d{5})","0549$1",,"\\d{6,10}","[05-7]\\d{7,9}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[5-7]",,],["(0549)(\\d{6})","$1 $2","0",,"($1) $2"],["(\\d{6})","0549 $1","[89]",,"(0549) $1"]]]',
 "235": '["TD","00|16",,,,,"\\d{8}","[2679]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
 "960": '["MV","0(?:0|19)",,,,,"\\d{7,10}","[3467]\\d{6}|9(?:00\\d{7}|\\d{6})",[["(\\d{3})(\\d{4})","$1-$2","[3467]|9(?:[1-9]|0[1-9])",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","900",,]]]',
-"221": '["SN","00",,,,,"\\d{9}","[378]\\d{8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[37]",,],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","8",,]]]',
+"221": '["SN","00",,,,,"\\d{9}","[3789]\\d{8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[379]",,],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","8",,]]]',
 "595": '["PY","00","0",,,,"\\d{5,9}","5[0-5]\\d{4,7}|[2-46-9]\\d{5,8}",[["(\\d{2})(\\d{5,7})","$1 $2","(?:[26]1|3[289]|4[124678]|7[123]|8[1236])","($FG)",],["(\\d{3})(\\d{3,6})","$1 $2","[2-9]0","$NP$FG",],["(\\d{3})(\\d{6})","$1 $2","9[1-9]","$NP$FG",],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","8700",,],["(\\d{3})(\\d{4,6})","$1 $2","[2-8][1-9]","($FG)",]]]',
 "977": '["NP","00","0",,,"$NP$FG","\\d{6,10}","[1-8]\\d{7}|9(?:[1-69]\\d{6}|7[2-6]\\d{5,7}|8\\d{8})",[["(1)(\\d{7})","$1-$2","1[2-6]",,],["(\\d{2})(\\d{6})","$1-$2","1[01]|[2-8]|9(?:[1-69]|7[15-9])",,],["(9\\d{2})(\\d{7})","$1-$2","9(?:7[45]|8)",,]]]',
 "36": '["HU","00","06",,,"($FG)","\\d{6,9}","[1-9]\\d{7,8}",[["(1)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","[2-9]",,]]]',
 };
--- a/dom/tests/mochitest/geolocation/mochitest.ini
+++ b/dom/tests/mochitest/geolocation/mochitest.ini
@@ -12,16 +12,18 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_cachedPosition.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT
 [test_cancelCurrent.html]
 skip-if = buildapp == 'b2g'
 [test_cancelWatch.html]
 skip-if = buildapp == 'b2g'
 [test_clearWatch.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
+[test_clearWatchBeforeAllowing.html]
+skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_clearWatch_invalid.html]
 skip-if = buildapp == 'b2g'
 [test_errorcheck.html]
 skip-if = toolkit=='gonk' || toolkit == 'android' || e10s #TIMED_OUT # b2g-debug(debug-only timeout)
 [test_geolocation_is_undefined_when_pref_is_off.html]
 [test_handlerSpinsEventLoop.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Don't run modal tests on Android # b2g(showmodaldialog) b2g-debug(showmodaldialog) b2g-desktop(showmodaldialog)
 [test_manyCurrentConcurrent.html]
@@ -42,9 +44,9 @@ skip-if = buildapp == 'b2g' || toolkit =
 skip-if = buildapp == 'b2g'
 [test_shutdown.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_timerRestartWatch.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT
 [test_windowClose.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_worseAccuracyDoesNotBlockCallback.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
+skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/geolocation/test_clearWatchBeforeAllowing.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=886026
+-->
+<head>
+  <title>Test for getCurrentPosition </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="geolocation_common.js"></script>
+
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank"
+href="https://bugzilla.mozilla.org/show_bug.cgi?id=886026">Mozilla Bug 886026</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+resume_geolocationProvider(function() {
+  force_prompt(true, run_test);
+});
+
+function run_test() {
+  var successCallbackCalled = false,
+  errorCallbackCalled = false;
+
+  var watchId = navigator.geolocation.watchPosition(
+    function(pos) {
+      successCallbackCalled = true;
+    }, function(err) {
+         errorCallbackCalled = true;
+       }
+  );
+
+  navigator.geolocation.getCurrentPosition(
+    function(pos) {
+      SimpleTest.executeSoon(function() {
+        ok(successCallbackCalled == false,
+        "getCurrentPosition : Success callback should not have been called");
+
+        ok(errorCallbackCalled == false,
+        "getCurrentPosition : Error callback should not have been called");
+
+        SimpleTest.finish();
+      });
+    }
+  );
+
+  navigator.geolocation.clearWatch(watchId);
+}
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -11,16 +11,17 @@
 #include "nsMenuBarListener.h"
 #include "nsContentUtils.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMXULElement.h"
 #include "nsIXULDocument.h"
 #include "nsIXULTemplateBuilder.h"
 #include "nsCSSFrameConstructor.h"
+#include "nsGlobalWindow.h"
 #include "nsLayoutUtils.h"
 #include "nsViewManager.h"
 #include "nsIComponentManager.h"
 #include "nsITimer.h"
 #include "nsFocusManager.h"
 #include "nsIDocShell.h"
 #include "nsPIDOMWindow.h"
 #include "nsIInterfaceRequestorUtils.h"
@@ -1589,28 +1590,28 @@ nsXULPopupManager::MayShowPopup(nsMenuPo
   if (widget && widget->GetLastRollup() == aPopup->GetContent())
       return false;
 
   nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
   nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
   if (!baseWin)
     return false;
 
+  nsCOMPtr<nsIDocShellTreeItem> root;
+  dsti->GetRootTreeItem(getter_AddRefs(root));
+  if (!root) {
+    return false;
+  }
+
+  nsCOMPtr<nsIDOMWindow> rootWin = root->GetWindow();
+
   // chrome shells can always open popups, but other types of shells can only
   // open popups when they are focused and visible
   if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
     // only allow popups in active windows
-    nsCOMPtr<nsIDocShellTreeItem> root;
-    dsti->GetRootTreeItem(getter_AddRefs(root));
-    if (!root) {
-      return false;
-    }
-
-    nsCOMPtr<nsIDOMWindow> rootWin = root->GetWindow();
-
     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
     if (!fm || !rootWin)
       return false;
 
     nsCOMPtr<nsIDOMWindow> activeWindow;
     fm->GetActiveWindow(getter_AddRefs(activeWindow));
     if (activeWindow != rootWin)
       return false;
@@ -1625,16 +1626,25 @@ nsXULPopupManager::MayShowPopup(nsMenuPo
   // platforms respond differently when an popup is opened in a minimized
   // window, so this is always disabled.
   nsCOMPtr<nsIWidget> mainWidget;
   baseWin->GetMainWidget(getter_AddRefs(mainWidget));
   if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
     return false;
   }
 
+#ifdef XP_MACOSX
+  if (rootWin) {
+    nsGlobalWindow *globalWin = static_cast<nsGlobalWindow *>(rootWin.get());
+    if (globalWin->IsInModalState()) {
+      return false;
+    }
+  }
+#endif
+
   // cannot open a popup that is a submenu of a menupopup that isn't open.
   nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
   if (menuFrame) {
     nsMenuParent* parentPopup = menuFrame->GetMenuParent();
     if (parentPopup && !parentPopup->IsOpen())
       return false;
   }
 
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -524,16 +524,17 @@ public class BrowserApp extends GeckoApp
         tabHistoryController = new TabHistoryController(new OnShowTabHistory() {
             @Override
             public void onShowHistory(final List<TabHistoryPage> historyPageList, final int toIndex) {
                 runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                         final TabHistoryFragment fragment = TabHistoryFragment.newInstance(historyPageList, toIndex);
                         final FragmentManager fragmentManager = getSupportFragmentManager();
+                        GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getInteger(R.integer.long_press_vibrate_msec));
                         fragment.show(R.id.tab_history_panel, fragmentManager.beginTransaction(), TAB_HISTORY_FRAGMENT_TAG);
                     }
                 });
             }
         });
         mBrowserToolbar.setTabHistoryController(tabHistoryController);
 
         final String action = intent.getAction();
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1410,16 +1410,23 @@ public class GeckoAppShell
         }
     }
 
     private static Vibrator vibrator() {
         LayerView layerView = getLayerView();
         return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
     }
 
+    // Vibrate only if haptic feedback is enabled.
+    public static void vibrateOnHapticFeedbackEnabled(long milliseconds) {
+        if (Settings.System.getInt(getContext().getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) > 0) {
+            vibrate(milliseconds);
+        }
+    }
+
     @WrapElementForJNI(stubName = "Vibrate1")
     public static void vibrate(long milliseconds) {
         sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
         sVibrationMaybePlaying = true;
         vibrator().vibrate(milliseconds);
     }
 
     @WrapElementForJNI(stubName = "VibrateA")
--- a/mobile/android/base/resources/values/integers.xml
+++ b/mobile/android/base/resources/values/integers.xml
@@ -3,10 +3,11 @@
    - 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/. -->
 
 <resources>
 
     <integer name="number_of_top_sites">6</integer>
     <integer name="number_of_top_sites_cols">2</integer>
     <integer name="max_icon_grid_columns">4</integer>
+    <integer name="long_press_vibrate_msec">100</integer>
 
 </resources>
--- a/mobile/android/base/tabs/TabStripView.java
+++ b/mobile/android/base/tabs/TabStripView.java
@@ -6,17 +6,17 @@
 package org.mozilla.gecko.tabs;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 
 import com.nineoldandroids.animation.Animator;
 import com.nineoldandroids.animation.AnimatorSet;
 import com.nineoldandroids.animation.ObjectAnimator;
@@ -28,18 +28,18 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.TwoWayView;
 
 public class TabStripView extends TwoWayView {
     private static final String LOGTAG = "GeckoTabStrip";
 
     private static final int ANIM_TIME_MS = 200;
-    private static final AccelerateDecelerateInterpolator ANIM_INTERPOLATOR =
-            new AccelerateDecelerateInterpolator();
+    private static final DecelerateInterpolator ANIM_INTERPOLATOR =
+            new DecelerateInterpolator();
 
     private final TabStripAdapter adapter;
     private final Drawable divider;
 
     // Filled by calls to ShapeDrawable.getPadding();
     // saved to prevent allocation in draw().
     private final Rect dividerPadding = new Rect();
 
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -3357,19 +3357,19 @@ nsDownload::FixTargetPermissions()
 
   // Set perms according to umask.
   nsCOMPtr<nsIPropertyBag2> infoService =
       do_GetService("@mozilla.org/system-info;1");
   uint32_t gUserUmask = 0;
   rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("umask"),
                                         &gUserUmask);
   if (NS_SUCCEEDED(rv)) {
-    rv = target->SetPermissions(0666 & ~gUserUmask);
+    (void)target->SetPermissions(0666 & ~gUserUmask);
   }
-  return rv;
+  return NS_OK;
 }
 
 nsresult
 nsDownload::MoveTempToTarget()
 {
   nsCOMPtr<nsIFile> target;
   nsresult rv = GetTargetFile(getter_AddRefs(target));
   NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -13,16 +13,17 @@ const { classes: Cc, interfaces: Ci, res
 
 const PREF_BRANCH = "browser.urlbar.";
 
 // Prefs are defined as [pref name, default value].
 const PREF_ENABLED =                [ "autocomplete.enabled",   true ];
 const PREF_AUTOFILL =               [ "autoFill",               true ];
 const PREF_AUTOFILL_TYPED =         [ "autoFill.typed",         true ];
 const PREF_AUTOFILL_SEARCHENGINES = [ "autoFill.searchEngines", true ];
+const PREF_RESTYLESEARCHES        = [ "restyleSearches",        false ];
 const PREF_DELAY =                  [ "delay",                  50 ];
 const PREF_BEHAVIOR =               [ "matchBehavior", MATCH_BOUNDARY_ANYWHERE ];
 const PREF_FILTER_JS =              [ "filter.javascript",      true ];
 const PREF_MAXRESULTS =             [ "maxRichResults",         25 ];
 const PREF_RESTRICT_HISTORY =       [ "restrict.history",       "^" ];
 const PREF_RESTRICT_BOOKMARKS =     [ "restrict.bookmark",      "*" ];
 const PREF_RESTRICT_TYPED =         [ "restrict.typed",         "~" ];
 const PREF_RESTRICT_TAG =           [ "restrict.tag",           "+" ];
@@ -379,16 +380,17 @@ XPCOMUtils.defineLazyGetter(this, "Prefs
     }
   }
 
   function loadPrefs(subject, topic, data) {
     store.enabled = prefs.get(...PREF_ENABLED);
     store.autofill = prefs.get(...PREF_AUTOFILL);
     store.autofillTyped = prefs.get(...PREF_AUTOFILL_TYPED);
     store.autofillSearchEngines = prefs.get(...PREF_AUTOFILL_SEARCHENGINES);
+    store.restyleSearches = prefs.get(...PREF_RESTYLESEARCHES);
     store.delay = prefs.get(...PREF_DELAY);
     store.matchBehavior = prefs.get(...PREF_BEHAVIOR);
     store.filterJavaScript = prefs.get(...PREF_FILTER_JS);
     store.maxRichResults = prefs.get(...PREF_MAXRESULTS);
     store.restrictHistoryToken = prefs.get(...PREF_RESTRICT_HISTORY);
     store.restrictBookmarkToken = prefs.get(...PREF_RESTRICT_BOOKMARKS);
     store.restrictTypedToken = prefs.get(...PREF_RESTRICT_TYPED);
     store.restrictTagToken = prefs.get(...PREF_RESTRICT_TAG);
@@ -1104,17 +1106,17 @@ Search.prototype = {
         this._usedPlaceIds.add(match.placeId);
       this._usedURLs.add(urlMapKey);
 
       if (!match.style) {
         match.style = "favicon";
       }
 
       // Restyle past searches, unless they are bookmarks or special results.
-      if (match.style == "favicon") {
+      if (Prefs.restyleSearches && match.style == "favicon") {
         this._maybeRestyleSearchMatch(match);
       }
 
       this._result.appendMatch(match.value,
                                match.comment,
                                match.icon || PlacesUtils.favicons.defaultFavicon.spec,
                                match.style,
                                match.finalCompleteValue || "");
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -15,41 +15,16 @@
 #include "nsUnicharUtils.h"
 #include "nsPrintfCString.h"
 #include "prprf.h"
 #include "mozilla/storage.h"
 
 #include "GeckoProfiler.h"
 
 #define BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH 32
-#define RECENT_BOOKMARKS_INITIAL_CACHE_LENGTH 10
-// Threshold to expire old bookmarks if the initial cache size is exceeded.
-#define RECENT_BOOKMARKS_THRESHOLD PRTime((int64_t)1 * 60 * PR_USEC_PER_SEC)
-
-#define BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(_itemId_) \
-  mUncachableBookmarks.PutEntry(_itemId_); \
-  mRecentBookmarksCache.RemoveEntry(_itemId_)
-
-#define END_CRITICAL_BOOKMARK_CACHE_SECTION(_itemId_) \
-  MOZ_ASSERT(!mRecentBookmarksCache.GetEntry(_itemId_)); \
-  MOZ_ASSERT(mUncachableBookmarks.GetEntry(_itemId_)); \
-  mUncachableBookmarks.RemoveEntry(_itemId_)
-
-#define ADD_TO_BOOKMARK_CACHE(_itemId_, _data_) \
-  PR_BEGIN_MACRO \
-  ExpireNonrecentBookmarks(&mRecentBookmarksCache); \
-  if (!mUncachableBookmarks.GetEntry(_itemId_)) { \
-    BookmarkKeyClass* key = mRecentBookmarksCache.PutEntry(_itemId_); \
-    if (key) { \
-      key->bookmark = _data_; \
-    } \
-  } \
-  PR_END_MACRO
-
-#define TOPIC_PLACES_MAINTENANCE "places-maintenance-finished"
 
 using namespace mozilla;
 
 // These columns sit to the right of the kGetInfoIndex_* columns.
 const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 15;
 const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 16;
 const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 17;
 const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 18;
@@ -150,73 +125,31 @@ public:
   }
 
 private:
   nsRefPtr<nsNavBookmarks> mBookmarksSvc;
   Method mCallback;
   DataType mData;
 };
 
-static PLDHashOperator
-ExpireNonrecentBookmarksCallback(BookmarkKeyClass* aKey,
-                                 void* userArg)
-{
-  int64_t* threshold = reinterpret_cast<int64_t*>(userArg);
-  if (aKey->creationTime < *threshold) {
-    return PL_DHASH_REMOVE;
-  }
-  return PL_DHASH_NEXT;
-}
-
-static void
-ExpireNonrecentBookmarks(nsTHashtable<BookmarkKeyClass>* hashTable)
-{
-  if (hashTable->Count() > RECENT_BOOKMARKS_INITIAL_CACHE_LENGTH) {
-    int64_t threshold = PR_Now() - RECENT_BOOKMARKS_THRESHOLD;
-    (void)hashTable->EnumerateEntries(ExpireNonrecentBookmarksCallback,
-                                      reinterpret_cast<void*>(&threshold));
-  }
-}
-
-static PLDHashOperator
-ExpireRecentBookmarksByParentCallback(BookmarkKeyClass* aKey,
-                                      void* userArg)
-{
-  int64_t* parentId = reinterpret_cast<int64_t*>(userArg);
-  if (aKey->bookmark.parentId == *parentId) {
-    return PL_DHASH_REMOVE;
-  }
-  return PL_DHASH_NEXT;
-}
-
-static void
-ExpireRecentBookmarksByParent(nsTHashtable<BookmarkKeyClass>* hashTable,
-                              int64_t aParentId)
-{
-  (void)hashTable->EnumerateEntries(ExpireRecentBookmarksByParentCallback,
-                                    reinterpret_cast<void*>(&aParentId));
-}
-
 } // Anonymous namespace.
 
 
 nsNavBookmarks::nsNavBookmarks()
   : mItemCount(0)
   , mRoot(0)
   , mMenuRoot(0)
   , mTagsRoot(0)
   , mUnfiledRoot(0)
   , mToolbarRoot(0)
   , mCanNotify(false)
   , mCacheObservers("bookmark-observers")
   , mBatching(false)
   , mBookmarkToKeywordHash(BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH)
   , mBookmarkToKeywordHashInitialized(false)
-  , mRecentBookmarksCache(RECENT_BOOKMARKS_INITIAL_CACHE_LENGTH)
-  , mUncachableBookmarks(RECENT_BOOKMARKS_INITIAL_CACHE_LENGTH)
 {
   NS_ASSERTION(!gBookmarksService,
                "Attempting to create two instances of the service!");
   gBookmarksService = this;
 }
 
 
 nsNavBookmarks::~nsNavBookmarks()
@@ -240,17 +173,16 @@ NS_IMPL_ISUPPORTS(nsNavBookmarks
 nsresult
 nsNavBookmarks::Init()
 {
   mDB = Database::GetDatabase();
   NS_ENSURE_STATE(mDB);
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
-    (void)os->AddObserver(this, TOPIC_PLACES_MAINTENANCE, true);
     (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true);
     (void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
   }
 
   nsresult rv = ReadRoots();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mCanNotify = true;
@@ -341,20 +273,16 @@ nsresult
 nsNavBookmarks::AdjustIndices(int64_t aFolderId,
                               int32_t aStartIndex,
                               int32_t aEndIndex,
                               int32_t aDelta)
 {
   NS_ASSERTION(aStartIndex >= 0 && aEndIndex <= INT32_MAX &&
                aStartIndex <= aEndIndex, "Bad indices");
 
-  // Expire all cached items for this parent, since all positions are going to
-  // change.
-  ExpireRecentBookmarksByParent(&mRecentBookmarksCache, aFolderId);
-
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "UPDATE moz_bookmarks SET position = position + :delta "
       "WHERE parent = :parent "
         "AND position BETWEEN :from_index AND :to_index"
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
@@ -547,18 +475,16 @@ nsNavBookmarks::InsertBookmarkInDB(int64
     bookmark.lastModified = aDateAdded;
   if (aURI) {
     rv = aURI->GetSpec(bookmark.url);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   bookmark.parentGuid = aParentGuid;
   bookmark.grandParentId = aGrandParentId;
 
-  ADD_TO_BOOKMARK_CACHE(*_itemId, bookmark);
-
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::InsertBookmark(int64_t aFolder,
                                nsIURI* aURI,
                                int32_t aIndex,
@@ -679,18 +605,16 @@ nsNavBookmarks::RemoveItem(int64_t aItem
   }
 
   if (bookmark.type == TYPE_FOLDER) {
     // Remove all of the folder's children.
     rv = RemoveFolderChildren(bookmark.id);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "DELETE FROM moz_bookmarks WHERE id = :item_id"
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -707,18 +631,16 @@ nsNavBookmarks::RemoveItem(int64_t aItem
   bookmark.lastModified = PR_Now();
   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.parentId,
                            bookmark.lastModified);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   nsCOMPtr<nsIURI> uri;
   if (bookmark.type == TYPE_BOOKMARK) {
     // If not a tag, recalculate frecency for this entry, since it changed.
     if (bookmark.grandParentId != mTagsRoot) {
       nsNavHistory* history = nsNavHistory::GetHistoryService();
       NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
       rv = history->UpdateFrecency(bookmark.placeId);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -1149,18 +1071,16 @@ nsNavBookmarks::RemoveFolderChildren(int
   nsCString foldersToRemove;
   for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
     BookmarkData& child = folderChildrenArray[i];
 
     if (child.type == TYPE_FOLDER) {
       foldersToRemove.Append(',');
       foldersToRemove.AppendInt(child.id);
     }
-
-    BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(child.id);
   }
 
   // Delete items from the database now.
   mozStorageTransaction transaction(mDB->MainConn(), false);
 
   nsCOMPtr<mozIStorageStatement> deleteStatement = mDB->GetStatement(
     NS_LITERAL_CSTRING(
       "DELETE FROM moz_bookmarks "
@@ -1197,17 +1117,16 @@ nsNavBookmarks::RemoveFolderChildren(int
         NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
         rv = history->UpdateFrecency(child.placeId);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       rv = UpdateKeywordsHashForRemovedBookmark(child.id);
       NS_ENSURE_SUCCESS(rv, rv);
     }
-    END_CRITICAL_BOOKMARK_CACHE_SECTION(child.id);
   }
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Call observers in reverse order to serve children before their parent.
   for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
     BookmarkData& child = folderChildrenArray[i];
@@ -1346,18 +1265,16 @@ nsNavBookmarks::MoveItem(int64_t aItemId
     // First, fill the hole from the removal from the old parent.
     rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, INT32_MAX, -1);
     NS_ENSURE_SUCCESS(rv, rv);
     // Now, make room in the new parent for the insertion.
     rv = AdjustIndices(aNewParent, newIndex, INT32_MAX, 1);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   {
     // Update parent and position.
     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
       "UPDATE moz_bookmarks SET parent = :parent, position = :item_index "
       "WHERE id = :item_id "
     );
     NS_ENSURE_STATE(stmt);
     mozStorageStatementScoper scoper(stmt);
@@ -1376,18 +1293,16 @@ nsNavBookmarks::MoveItem(int64_t aItemId
   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.parentId, now);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = SetItemDateInternal(LAST_MODIFIED, aNewParent, now);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemMoved(bookmark.id,
                                bookmark.parentId,
                                bookmark.position,
                                aNewParent,
                                newIndex,
                                bookmark.type,
@@ -1396,24 +1311,16 @@ nsNavBookmarks::MoveItem(int64_t aItemId
                                newParentGuid));
   return NS_OK;
 }
 
 nsresult
 nsNavBookmarks::FetchItemInfo(int64_t aItemId,
                               BookmarkData& _bookmark)
 {
-  // Check if the requested id is in the recent cache and avoid the database
-  // lookup if so.  Invalidate the cache after getting data if requested.
-  BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aItemId);
-  if (key) {
-    _bookmark = key->bookmark;
-    return NS_OK;
-  }
-
   // LEFT JOIN since not all bookmarks have an associated place.
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
            "b.dateAdded, b.lastModified, b.guid, t.guid, t.parent "
     "FROM moz_bookmarks b "
     "LEFT JOIN moz_bookmarks t ON t.id = b.parent "
     "LEFT JOIN moz_places h ON h.id = b.fk "
     "WHERE b.id = :item_id"
@@ -1466,18 +1373,16 @@ nsNavBookmarks::FetchItemInfo(int64_t aI
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->GetInt64(11, &_bookmark.grandParentId);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   else {
     _bookmark.grandParentId = -1;
   }
 
-  ADD_TO_BOOKMARK_CACHE(aItemId, _bookmark);
-
   return NS_OK;
 }
 
 nsresult
 nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
                                     int64_t aItemId,
                                     PRTime aValue)
 {
@@ -1502,26 +1407,16 @@ nsNavBookmarks::SetItemDateInternal(enum
   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Update the cache entry, if needed.
-  BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aItemId);
-  if (key) {
-    if (aDateType == DATE_ADDED) {
-      key->bookmark.dateAdded = aValue;
-    }
-    // Set lastModified in both cases.
-    key->bookmark.lastModified = aValue;
-  }
-
   // note, we are not notifying the observers
   // that the item has changed.
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
@@ -1645,28 +1540,16 @@ nsNavBookmarks::SetItemTitle(int64_t aIt
                                   bookmark.lastModified);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Update the cache entry, if needed.
-  BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aItemId);
-  if (key) {
-    if (title.IsVoid()) {
-      key->bookmark.title.SetIsVoid(true);
-    }
-    else {
-      key->bookmark.title.Assign(title);
-    }
-    key->bookmark.lastModified = bookmark.lastModified;
-  }
-
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemChanged(bookmark.id,
                                  NS_LITERAL_CSTRING("title"),
                                  false,
                                  title,
                                  bookmark.lastModified,
                                  bookmark.type,
@@ -2104,18 +1987,16 @@ nsNavBookmarks::ChangeBookmarkURI(int64_
   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
   int64_t newPlaceId;
   nsAutoCString newPlaceGuid;
   rv = history->GetOrCreateIdForPage(aNewURI, &newPlaceId, newPlaceGuid);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!newPlaceId)
     return NS_ERROR_INVALID_ARG;
 
-  BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
     "UPDATE moz_bookmarks SET fk = :page_id, lastModified = :date "
     "WHERE id = :item_id "
   );
   NS_ENSURE_STATE(statement);
   mozStorageStatementScoper scoper(statement);
 
   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), newPlaceId);
@@ -2127,18 +2008,16 @@ nsNavBookmarks::ChangeBookmarkURI(int64_
   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   rv = history->UpdateFrecency(newPlaceId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Upon changing the URI for a bookmark, update the frecency for the old
   // place as well.
   rv = history->UpdateFrecency(bookmark.placeId);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2340,34 +2219,30 @@ nsNavBookmarks::SetItemIndex(int64_t aIt
   int64_t grandParentId;
   nsAutoCString folderGuid;
   rv = FetchFolderInfo(bookmark.parentId, &folderCount, folderGuid, &grandParentId);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(aNewIndex < folderCount, NS_ERROR_INVALID_ARG);
   // Check the parent's guid is the expected one.
   MOZ_ASSERT(bookmark.parentGuid == folderGuid);
 
-  BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "UPDATE moz_bookmarks SET position = :item_index WHERE id = :item_id"
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aNewIndex);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
-
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemMoved(bookmark.id,
                                bookmark.parentId,
                                bookmark.position,
                                bookmark.parentId,
                                aNewIndex,
                                bookmark.type,
@@ -2490,22 +2365,16 @@ nsNavBookmarks::SetKeywordForBookmark(in
                                            bookmark.id);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = updateBookmarkStmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Update the cache entry, if needed.
-  BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aBookmarkId);
-  if (key) {
-    key->bookmark.lastModified = bookmark.lastModified;
-  }
-
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemChanged(bookmark.id,
                                  NS_LITERAL_CSTRING("keyword"),
                                  false,
                                  NS_ConvertUTF16toUTF8(keyword),
                                  bookmark.lastModified,
                                  bookmark.type,
@@ -2753,22 +2622,17 @@ nsNavBookmarks::NotifyItemChanged(const 
 //// nsIObserver
 
 NS_IMETHODIMP
 nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
                         const char16_t *aData)
 {
   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
 
-  if (strcmp(aTopic, TOPIC_PLACES_MAINTENANCE) == 0) {
-    // Maintenance can execute direct writes to the database, thus clear all
-    // the cached bookmarks.
-    mRecentBookmarksCache.Clear();
-  }
-  else if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
+  if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
     // Stop Observing annotations.
     nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
     if (annosvc) {
       annosvc->RemoveObserver(this);
     }
   }
   else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
     // Don't even try to notify observers from this point on, the category
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -56,34 +56,16 @@ namespace places {
     nsCString property;
     bool isAnnotation;
     nsCString newValue;
   };
 
   typedef void (nsNavBookmarks::*ItemVisitMethod)(const ItemVisitData&);
   typedef void (nsNavBookmarks::*ItemChangeMethod)(const ItemChangeData&);
 
-  class BookmarkKeyClass : public nsTrimInt64HashKey
-  {
-    public:
-    explicit BookmarkKeyClass(const int64_t* aItemId)
-    : nsTrimInt64HashKey(aItemId)
-    , creationTime(PR_Now())
-    {
-    }
-    BookmarkKeyClass(const BookmarkKeyClass& aOther)
-    : nsTrimInt64HashKey(aOther)
-    , creationTime(PR_Now())
-    {
-      NS_NOTREACHED("Do not call me!");
-    }
-    BookmarkData bookmark;
-    PRTime creationTime;
-  };
-
   enum BookmarkDate {
     DATE_ADDED = 0
   , LAST_MODIFIED
   };
 
 } // namespace places
 } // namespace mozilla
 
@@ -119,17 +101,16 @@ public:
       NS_ENSURE_TRUE(serv, nullptr);
       NS_ASSERTION(gBookmarksService,
                    "Should have static instance pointer now");
     }
     return gBookmarksService;
   }
 
   typedef mozilla::places::BookmarkData BookmarkData;
-  typedef mozilla::places::BookmarkKeyClass BookmarkKeyClass;
   typedef mozilla::places::ItemVisitData ItemVisitData;
   typedef mozilla::places::ItemChangeData ItemChangeData;
   typedef mozilla::places::BookmarkStatementId BookmarkStatementId;
 
   nsresult ResultNodeForContainer(int64_t aID,
                                   nsNavHistoryQueryOptions* aOptions,
                                   nsNavHistoryResultNode** aNode);
 
@@ -450,23 +431,11 @@ private:
 
   /**
    * This function must be called every time a bookmark is removed.
    *
    * @param aURI
    *        Uri to test.
    */
   nsresult UpdateKeywordsHashForRemovedBookmark(int64_t aItemId);
-
-  /**
-   * Cache for the last fetched BookmarkData entries.
-   * This is used to speed up repeated requests to the same item id.
-   */
-  nsTHashtable<BookmarkKeyClass> mRecentBookmarksCache;
-
-  /**
-   * Tracks bookmarks in the cache critical path.  Items should not be
-   * added to the cache till they are removed from this hash.
-   */
-  nsTHashtable<nsTrimInt64HashKey> mUncachableBookmarks;
 };
 
 #endif // nsNavBookmarks_h_
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -3503,36 +3503,48 @@ nsNavHistoryFolderResultNode::OnItemAdde
                                           nsIURI* aURI,
                                           const nsACString& aTitle,
                                           PRTime aDateAdded,
                                           const nsACString& aGUID,
                                           const nsACString& aParentGUID)
 {
   NS_ASSERTION(aParentFolder == mItemId, "Got wrong bookmark update");
 
+  RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+  {
+    uint32_t index;
+    nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+    // Bug 1097528.
+    // It's possible our result registered due to a previous notification, for
+    // example the Library left pane could have refreshed and replaced the
+    // right pane as a consequence. In such a case our contents are already
+    // up-to-date.  That's OK.
+    if (node)
+      return NS_OK;
+  }
+
   bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
-                        (mParent && mParent->mOptions->ExcludeItems()) ||
-                        mOptions->ExcludeItems();
+                      (mParent && mParent->mOptions->ExcludeItems()) ||
+                      mOptions->ExcludeItems();
 
   // here, try to do something reasonable if the bookmark service gives us
   // a bogus index.
   if (aIndex < 0) {
     NS_NOTREACHED("Invalid index for item adding: <0");
     aIndex = 0;
   }
   else if (aIndex > mChildren.Count()) {
     if (!excludeItems) {
       // Something wrong happened while updating indexes.
       NS_NOTREACHED("Invalid index for item adding: greater than count");
     }
     aIndex = mChildren.Count();
   }
 
-  RESTART_AND_RETURN_IF_ASYNC_PENDING();
-
   nsresult rv;
 
   // Check for query URIs, which are bookmarks, but treated as containers
   // in results and views.
   bool isQuery = false;
   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
     NS_ASSERTION(aURI, "Got a null URI when we are a bookmark?!");
     nsAutoCString itemURISpec;
@@ -3601,33 +3613,33 @@ nsNavHistoryFolderResultNode::OnItemRemo
   // its list.
   if (mItemId == aItemId)
     return NS_OK;
 
   NS_ASSERTION(aParentFolder == mItemId, "Got wrong bookmark update");
 
   RESTART_AND_RETURN_IF_ASYNC_PENDING();
 
-  bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
-                        (mParent && mParent->mOptions->ExcludeItems()) ||
-                        mOptions->ExcludeItems();
-
   // don't trust the index from the bookmark service, find it ourselves.  The
   // sorting could be different, or the bookmark services indices and ours might
   // be out of sync somehow.
   uint32_t index;
   nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+    // Bug 1097528.
+    // It's possible our result registered due to a previous notification, for
+    // example the Library left pane could have refreshed and replaced the
+    // right pane as a consequence. In such a case our contents are already
+    // up-to-date.  That's OK.
   if (!node) {
-    if (excludeItems)
-      return NS_OK;
-
-    NS_NOTREACHED("Removing item we don't have");
-    return NS_ERROR_FAILURE;
+    return NS_OK;
   }
 
+  bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+                        (mParent && mParent->mOptions->ExcludeItems()) ||
+                        mOptions->ExcludeItems();
   if ((node->IsURI() || node->IsSeparator()) && excludeItems) {
     // don't update items when we aren't displaying them, but we do need to
     // adjust everybody's bookmark indices to account for the removal
     ReindexRange(aIndex, INT32_MAX, -1);
     return NS_OK;
   }
 
   if (!StartIncrementalUpdate())
@@ -3849,34 +3861,52 @@ nsNavHistoryFolderResultNode::OnItemMove
                                           const nsACString& aOldParentGUID,
                                           const nsACString& aNewParentGUID)
 {
   NS_ASSERTION(aOldParent == mItemId || aNewParent == mItemId,
                "Got a bookmark message that doesn't belong to us");
 
   RESTART_AND_RETURN_IF_ASYNC_PENDING();
 
+  uint32_t index;
+  nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+  // Bug 1097528.
+  // It's possible our result registered due to a previous notification, for
+  // example the Library left pane could have refreshed and replaced the
+  // right pane as a consequence. In such a case our contents are already
+  // up-to-date.  That's OK.
+  if (node && aNewParent == mItemId && index == static_cast<uint32_t>(aNewIndex))
+    return NS_OK;
+  if (!node && aOldParent == mItemId)
+    return NS_OK;
+
+  bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+                      (mParent && mParent->mOptions->ExcludeItems()) ||
+                      mOptions->ExcludeItems();
+  if (node && excludeItems && (node->IsURI() || node->IsSeparator())) {
+    // Don't update items when we aren't displaying them.
+    return NS_OK;
+  }
+
   if (!StartIncrementalUpdate())
     return NS_OK; // entire container was refreshed for us
 
   if (aOldParent == aNewParent) {
     // getting moved within the same folder, we don't want to do a remove and
     // an add because that will lose your tree state.
 
     // adjust bookmark indices
     ReindexRange(aOldIndex + 1, INT32_MAX, -1);
     ReindexRange(aNewIndex, INT32_MAX, 1);
 
-    uint32_t index;
-    nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+    MOZ_ASSERT(node, "Can't find folder that is moving!");
     if (!node) {
-      NS_NOTREACHED("Can't find folder that is moving!");
       return NS_ERROR_FAILURE;
     }
-    NS_ASSERTION(index < uint32_t(mChildren.Count()), "Invalid index!");
+    MOZ_ASSERT(index < uint32_t(mChildren.Count()), "Invalid index!");
     node->mBookmarkIndex = aNewIndex;
 
     // adjust position
     EnsureItemPosition(index);
     return NS_OK;
   } else {
     // moving between two different folders, just do a remove and an add
     nsCOMPtr<nsIURI> itemURI;
--- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_restyle.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_restyle.js
@@ -10,16 +10,25 @@ add_task(function* test_searchEngine() {
   do_register_cleanup(() => Services.search.removeEngine(engine));
 
   let uri1 = NetUtil.newURI("http://s.example.com/search?q=Terms&client=1");
   let uri2 = NetUtil.newURI("http://s.example.com/search?q=Terms&client=2");
   yield promiseAddVisits({ uri: uri1, title: "Terms - SearchEngine Search" });
   addBookmark({ uri: uri2, title: "Terms - SearchEngine Search" });
 
   do_log_info("Past search terms should be styled, unless bookmarked");
+  Services.prefs.setBoolPref("browser.urlbar.restyleSearches", true);
   yield check_autocomplete({
     search: "term",
     matches: [ { uri: uri1, title: "Terms", searchEngine: "SearchEngine", style: ["favicon", "search"] },
                { uri: uri2, title: "Terms - SearchEngine Search", style: ["bookmark"] } ]
   });
 
+  do_log_info("Past search terms should not be styled if restyling is disabled");
+  Services.prefs.setBoolPref("browser.urlbar.restyleSearches", false);
+  yield check_autocomplete({
+    search: "term",
+    matches: [ { uri: uri1, title: "Terms - SearchEngine Search" },
+               { uri: uri2, title: "Terms - SearchEngine Search", style: ["bookmark"] } ]
+  });
+
   yield cleanup();
 });
--- a/toolkit/devtools/moz.build
+++ b/toolkit/devtools/moz.build
@@ -40,8 +40,12 @@ EXTRA_JS_MODULES.devtools += [
 
 EXTRA_JS_MODULES.devtools += [
     'Console.jsm',
     'DevToolsUtils.jsm',
     'LayoutHelpers.jsm',
     'Loader.jsm',
     'Require.jsm',
 ]
+
+EXTRA_JS_MODULES.devtools.server.actors += [
+    'server/actors/highlighter.css'
+]
rename from browser/themes/shared/devtools/highlighter.css
rename to toolkit/devtools/server/actors/highlighter.css
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -17,17 +17,17 @@ Cu.import("resource://gre/modules/devtoo
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // FIXME: add ":visited" and ":link" after bug 713106 is fixed
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
 const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"];
 const SVG_NS = "http://www.w3.org/2000/svg";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const HIGHLIGHTER_STYLESHEET_URI = "chrome://browser/skin/devtools/highlighter.css";
+const HIGHLIGHTER_STYLESHEET_URI = "resource://gre/modules/devtools/server/actors/highlighter.css";
 const HIGHLIGHTER_PICKED_TIMER = 1000;
 // How high is the nodeinfobar
 const NODE_INFOBAR_HEIGHT = 40; //px
 const NODE_INFOBAR_ARROW_SIZE = 15; // px
 // Width of boxmodelhighlighter guides
 const GUIDE_STROKE_WIDTH = 1;
 // The minimum distance a line should be before it has an arrow marker-end
 const ARROW_LINE_MIN_DISTANCE = 10;
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -172,16 +172,37 @@ BreakpointStore.prototype = {
           delete this._wholeLineBreakpoints[url][line];
           this._size--;
         }
       }
     }
   },
 
   /**
+   * Move the breakpoint to the new location.
+   *
+   * @param Object aBreakpoint
+   *        The breakpoint being moved. See `addBreakpoint` for a description of
+   *        its expected properties.
+   * @param Object aNewLocation
+   *        The location to move the breakpoint to. Properties:
+   *          - line
+   *          - column (optional; omission implies whole line breakpoint)
+   */
+  moveBreakpoint: function (aBreakpoint, aNewLocation) {
+    const existingBreakpoint = this.getBreakpoint(aBreakpoint);
+    this.removeBreakpoint(existingBreakpoint);
+
+    const { line, column } = aNewLocation;
+    existingBreakpoint.line = line;
+    existingBreakpoint.column = column;
+    this.addBreakpoint(existingBreakpoint);
+  },
+
+  /**
    * Get a breakpoint from the breakpoint store. Will throw an error if the
    * breakpoint is not found.
    *
    * @param Object aLocation
    *        The location of the breakpoint you are retrieving. It is an object
    *        with the following properties:
    *          - url
    *          - line
@@ -1421,222 +1442,291 @@ ThreadActor.prototype = {
    */
   _createAndStoreBreakpoint: function (aLocation) {
     // Add the breakpoint to the store for later reuse, in case it belongs to a
     // script that hasn't appeared yet.
     this.breakpointStore.addBreakpoint(aLocation);
     return this._setBreakpoint(aLocation);
   },
 
+
   /**
-   * Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint
-   * is being set contains no code, then the breakpoint will slide down to the
-   * next line that has runnable code. In this case the server breakpoint cache
-   * will be updated, so callers that iterate over the breakpoint cache should
-   * take that into account.
+   * Get or create the BreakpointActor for the breakpoint at the given location.
+   *
+   * NB: This will override a pre-existing BreakpointActor's condition with
+   * the given the location's condition.
+   *
+   * @param Object location
+   *        The breakpoint location. See BreakpointStore.prototype.addBreakpoint
+   *        for more information.
+   * @returns BreakpointActor
+   */
+  _getOrCreateBreakpointActor: function (location) {
+    let actor;
+    const storedBp = this.breakpointStore.getBreakpoint(location);
+
+    if (storedBp.actor) {
+      actor = storedBp.actor;
+      actor.condition = location.condition;
+      return actor;
+    }
+
+    storedBp.actor = actor = new BreakpointActor(this, {
+      url: location.url,
+      line: location.line,
+      column: location.column,
+      condition: location.condition
+    });
+    this.threadLifetimePool.addActor(actor);
+    return actor;
+  },
+
+  /**
+   * Set breakpoints at the offsets closest to our target location's column.
+   *
+   * @param Array scripts
+   *        The set of Debugger.Script instances to consider.
+   * @param Object location
+   *        The target location.
+   * @param BreakpointActor actor
+   *        The BreakpointActor to handle hitting the breakpoints we set.
+   * @returns Object
+   *          The RDP response.
+   */
+  _setBreakpointAtColumn: function (scripts, location, actor) {
+    // Debugger.Script -> array of offset mappings
+    const scriptsAndOffsetMappings = new Map();
+
+    for (let script of scripts) {
+      this._findClosestOffsetMappings(location, script, scriptsAndOffsetMappings);
+    }
+
+    for (let [script, mappings] of scriptsAndOffsetMappings) {
+      for (let offsetMapping of mappings) {
+        script.setBreakpoint(offsetMapping.offset, actor);
+      }
+      actor.addScript(script, this);
+    }
+
+    return {
+      actor: actor.actorID
+    };
+  },
+
+  /**
+   * Find the scripts which contain offsets that are an entry point to the given
+   * line.
+   *
+   * @param Array scripts
+   *        The set of Debugger.Scripts to consider.
+   * @param Number line
+   *        The line we are searching for entry points into.
+   * @returns Array of objects of the form { script, offsets } where:
+   *          - script is a Debugger.Script
+   *          - offsets is an array of offsets that are entry points into the
+   *            given line.
+   */
+  _findEntryPointsForLine: function (scripts, line) {
+    const entryPoints = [];
+    for (let script of scripts) {
+      const offsets = script.getLineOffsets(line);
+      if (offsets.length) {
+        entryPoints.push({ script, offsets });
+      }
+    }
+    return entryPoints;
+  },
+
+  /**
+   * Find the first line that is associated with bytecode offsets, and is
+   * greater than or equal to the given start line.
+   *
+   * @param Array scripts
+   *        The set of Debugger.Script instances to consider.
+   * @param Number startLine
+   *        The target line.
+   * @return Object|null
+   *         If we can't find a line matching our constraints, return
+   *         null. Otherwise, return an object of the form:
+   *           {
+   *             line: Number,
+   *             entryPoints: [
+   *               { script: Debugger.Script, offsets: [offset, ...] },
+   *               ...
+   *             ]
+   *           }
+   */
+  _findNextLineWithOffsets: function (scripts, startLine) {
+    const maxLine = Math.max(...scripts.map(s => s.startLine + s.lineCount));
+
+    for (let line = startLine; line < maxLine; line++) {
+      const entryPoints = this._findEntryPointsForLine(scripts, line);
+      if (entryPoints.length) {
+        return { line, entryPoints };
+      }
+    }
+
+    return null;
+  },
+
+  /**
+   * Set a breakpoint using the Debugger API. If the line on which the
+   * breakpoint is being set contains no code, then the breakpoint will slide
+   * down to the next line that has runnable code. In this case the server
+   * breakpoint cache will be updated, so callers that iterate over the
+   * breakpoint cache should take that into account.
    *
    * @param object aLocation
    *        The location of the breakpoint (in the generated source, if source
    *        mapping).
    * @param Debugger.Script aOnlyThisScript [optional]
    *        If provided, only set breakpoints in this Debugger.Script, and
    *        nowhere else.
    */
   _setBreakpoint: function (aLocation, aOnlyThisScript=null) {
-    let location = {
+    const location = {
       url: aLocation.url,
       line: aLocation.line,
       column: aLocation.column,
       condition: aLocation.condition
     };
-
-    let actor;
-    let storedBp = this.breakpointStore.getBreakpoint(location);
-    if (storedBp.actor) {
-      actor = storedBp.actor;
-      actor.condition = location.condition;
-    } else {
-      storedBp.actor = actor = new BreakpointActor(this, {
-        url: location.url,
-        line: location.line,
-        column: location.column,
-        condition: location.condition
-      });
-      this.threadLifetimePool.addActor(actor);
-    }
-
-    // Find all scripts matching the given location
-    let scripts = this.dbg.findScripts(location);
-    if (scripts.length == 0) {
+    const actor = location.actor = this._getOrCreateBreakpointActor(location);
+    const scripts = this.dbg.findScripts({
+      url: location.url,
+      // Although we will automatically slide the breakpoint down to the first
+      // line with code when the requested line doesn't have any, we want to
+      // restrict the sliding to within functions that contain the requested
+      // line.
+      line: location.line
+    });
+
+    if (scripts.length === 0) {
       // Since we did not find any scripts to set the breakpoint on now, return
       // early. When a new script that matches this breakpoint location is
-      // introduced, the breakpoint actor will already be in the breakpoint store
-      // and will be set at that time.
-      return {
-        actor: actor.actorID
-      };
-    }
-
-   /**
-    * For each script, if the given line has at least one entry point, set a
-    * breakpoint on the bytecode offets for each of them.
-    */
-
-    // Debugger.Script -> array of offset mappings
-    let scriptsAndOffsetMappings = new Map();
-
-    for (let script of scripts) {
-      this._findClosestOffsetMappings(location,
-                                      script,
-                                      scriptsAndOffsetMappings);
-    }
-
-    if (scriptsAndOffsetMappings.size > 0) {
-      for (let [script, mappings] of scriptsAndOffsetMappings) {
-        if (aOnlyThisScript && script !== aOnlyThisScript) {
-          continue;
-        }
-
-        for (let offsetMapping of mappings) {
-          script.setBreakpoint(offsetMapping.offset, actor);
-        }
-        actor.addScript(script, this);
-      }
-
+      // introduced, the breakpoint actor will already be in the breakpoint
+      // store and the breakpoint will be set at that time. This is similar to
+      // GDB's "pending" breakpoints for shared libraries that aren't loaded
+      // yet.
       return {
         actor: actor.actorID
       };
     }
 
-   /**
-    * If we get here, no breakpoint was set. This is because the given line
-    * has no entry points, for example because it is empty. As a fallback
-    * strategy, we try to set the breakpoint on the smallest line greater
-    * than or equal to the given line that as at least one entry point.
-    */
-
-    // Find all innermost scripts matching the given location
-    scripts = this.dbg.findScripts({
-      url: aLocation.url,
-      line: aLocation.line,
-      innermost: true
-    });
-
-    /**
-     * For each innermost script, look for the smallest line greater than or
-     * equal to the given line that has one or more entry points. If found, set
-     * a breakpoint on the bytecode offset for each of its entry points.
-     */
-    let actualLocation;
-    let found = false;
-    for (let script of scripts) {
-      let offsets = script.getAllOffsets();
-
-      for (let line = location.line; line < offsets.length; ++line) {
-        if (offsets[line]) {
-          if (!aOnlyThisScript || script === aOnlyThisScript) {
-            for (let offset of offsets[line]) {
-              script.setBreakpoint(offset, actor);
-            }
-            actor.addScript(script, this);
-          }
-
-          if (!actualLocation) {
-            actualLocation = {
-              url: location.url,
-              line: line
-            };
-          }
-
-          found = true;
-          break;
-        }
-      }
-    }
-
-    if (found) {
-      let existingBp = this.breakpointStore.hasBreakpoint(actualLocation);
-
-      if (existingBp && existingBp.actor) {
-        /**
-         * We already have a breakpoint actor for the actual location, so actor
-         * we created earlier is now redundant. Delete it, update the breakpoint
-         * store, and return the actor for the actual location.
-         */
+    if (location.column) {
+      return this._setBreakpointAtColumn(scripts, location, actor);
+    }
+
+    // Select the first line that has offsets, and is greater than or equal to
+    // the requested line. Set breakpoints on each of the offsets that is an
+    // entry point to our selected line.
+
+    const result = this._findNextLineWithOffsets(scripts, location.line);
+    if (!result) {
+      return {
+        error: "noCodeAtLineColumn",
+        actor: actor.actorID
+      };
+    }
+
+    const { line, entryPoints } = result;
+    const actualLocation = line !== location.line
+      ? { url: location.url, line }
+      : undefined;
+
+    if (actualLocation) {
+      // Check whether we already have a breakpoint actor for the actual
+      // location. If we do have an existing actor, then the actor we created
+      // above is redundant and must be destroyed. If we do not have an existing
+      // actor, we need to update the breakpoint store with the new location.
+
+      const existingBreakpoint = this.breakpointStore.hasBreakpoint(actualLocation);
+      if (existingBreakpoint && existingBreakpoint.actor) {
         actor.onDelete();
         this.breakpointStore.removeBreakpoint(location);
         return {
-          actor: existingBp.actor.actorID,
-          actualLocation: actualLocation
+          actor: existingBreakpoint.actor.actorID,
+          actualLocation
         };
+      } else {
+        actor.location = actualLocation;
+        this.breakpointStore.moveBreakpoint(location, actualLocation);
       }
-
-      /**
-       * We don't have a breakpoint actor for the actual location yet. Instead
-       * or creating a new actor, reuse the actor we created earlier, and update
-       * the breakpoint store.
-       */
-      actor.location = actualLocation;
-      this.breakpointStore.addBreakpoint({
-        actor: actor,
-        url: actualLocation.url,
-        line: actualLocation.line,
-        column: actualLocation.column
-      });
-      this.breakpointStore.removeBreakpoint(location);
-      return {
-        actor: actor.actorID,
-        actualLocation: actualLocation
-      };
-    }
-
-    /**
-     * If we get here, no line matching the given line was found, so just fail
-     * epically.
-     */
+    }
+
+    this._setBreakpointOnEntryPoints(
+      actor,
+      aOnlyThisScript
+        ? entryPoints.filter(o => o.script === aOnlyThisScript)
+        : entryPoints
+    );
+
     return {
-      error: "noCodeAtLineColumn",
-      actor: actor.actorID
+      actor: actor.actorID,
+      actualLocation
     };
   },
 
   /**
+   * Set breakpoints on all the given entry points with the given
+   * BreakpointActor as the handler.
+   *
+   * @param BreakpointActor actor
+   *        The actor handling the breakpoint hits.
+   * @param Array entryPoints
+   *        An array of objects of the form `{ script, offsets }`.
+   */
+  _setBreakpointOnEntryPoints: function (actor, entryPoints) {
+    for (let { script, offsets } of entryPoints) {
+      for (let offset of offsets) {
+        script.setBreakpoint(offset, actor);
+      }
+      actor.addScript(script, this);
+    }
+  },
+
+  /**
    * Find all of the offset mappings associated with `aScript` that are closest
    * to `aTargetLocation`. If new offset mappings are found that are closer to
    * `aTargetOffset` than the existing offset mappings inside
    * `aScriptsAndOffsetMappings`, we empty that map and only consider the
-   * closest offset mappings. If there is no column in `aTargetLocation`, we add
-   * all offset mappings that are on the given line.
+   * closest offset mappings.
+   *
+   * In many cases, but not all, this method finds only one closest offset.
+   * Consider the following case, where multiple offsets will be found:
+   *
+   *     0         1         2         3
+   *     0123456789012345678901234567890
+   *    +-------------------------------
+   *   1|function f() {
+   *   2|  return g() + h();
+   *   3|}
+   *
+   * The Debugger reports three offsets on line 2 upon which we could set a
+   * breakpoint: the `return` statement at column 2, the call expression `g()`
+   * at column 9, and the call expression `h()` at column 15. (Careful readers
+   * will note that complete source location information isn't saved by
+   * SpiderMonkey's frontend, and we don't get an offset associated specifically
+   * with the `+` operation.)
+   *
+   * If our target location is line 2 column 12, the offset for the call to `g`
+   * is 3 columns to the left and the offset for the call to `h` is 3 columns to
+   * the right. Because they are equally close, we will return both offsets to
+   * have breakpoints set upon them.
    *
    * @param Object aTargetLocation
    *        An object of the form { url, line[, column] }.
    * @param Debugger.Script aScript
    *        The script in which we are searching for offsets.
    * @param Map aScriptsAndOffsetMappings
    *        A Map object which maps Debugger.Script instances to arrays of
    *        offset mappings. This is an out param.
    */
   _findClosestOffsetMappings: function (aTargetLocation,
                                         aScript,
                                         aScriptsAndOffsetMappings) {
-    // If we are given a column, we will try and break only at that location,
-    // otherwise we will break anytime we get on that line.
-
-    if (aTargetLocation.column == null) {
-      let offsetMappings = aScript.getLineOffsets(aTargetLocation.line)
-        .map(o => ({
-          line: aTargetLocation.line,
-          offset: o
-        }));
-      if (offsetMappings.length) {
-        aScriptsAndOffsetMappings.set(aScript, offsetMappings);
-      }
-      return;
-    }
-
     let offsetMappings = aScript.getAllColumnOffsets()
       .filter(({ lineNumber }) => lineNumber === aTargetLocation.line);
 
     // Attempt to find the current closest offset distance from the target
     // location by grabbing any offset mapping in the map by doing one iteration
     // and then breaking (they all have the same distance from the target
     // location).
     let closestDistance = Infinity;
--- a/toolkit/devtools/server/tests/unit/test_breakpoint-20.js
+++ b/toolkit/devtools/server/tests/unit/test_breakpoint-20.js
@@ -11,20 +11,20 @@ var gClient;
 var gTraceClient;
 var gThreadClient;
 
 Components.utils.import('resource:///modules/devtools/SourceMap.jsm');
 
 function run_test()
 {
   initTestTracerServer();
-  gDebuggee = addTestGlobal("test-tracer-actor");
+  gDebuggee = addTestGlobal("test-breakpoints");
   gClient = new DebuggerClient(DebuggerServer.connectPipe());
   gClient.connect(function() {
-    attachTestThread(gClient, "test-tracer-actor", testBreakpoint);
+    attachTestThread(gClient, "test-breakpoints", testBreakpoint);
   });
   do_test_pending();
 }
 
 const testBreakpoint = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) {
   evalSetupCode();
 
   // Load the test source once.
--- a/toolkit/devtools/server/tests/unit/test_breakpointstore.js
+++ b/toolkit/devtools/server/tests/unit/test_breakpointstore.js
@@ -11,16 +11,17 @@ function run_test()
   Cu.import("resource://gre/modules/jsdebugger.jsm");
   addDebuggerToGlobal(this);
 
   test_has_breakpoint();
   test_add_breakpoint();
   test_remove_breakpoint();
   test_find_breakpoints();
   test_duplicate_breakpoints();
+  test_move_breakpoint();
 }
 
 function test_has_breakpoint() {
   let bpStore = new BreakpointStore();
   let location = {
     url: "http://example.com/foo.js",
     line: 3
   };
@@ -175,8 +176,36 @@ function test_duplicate_breakpoints() {
     url: "http://example.com/foo.js",
     line: 15
   };
   bpStore.addBreakpoint(location);
   bpStore.addBreakpoint(location);
   do_check_eq(bpStore.size, 1, "We should have only 1 whole line breakpoint");
   bpStore.removeBreakpoint(location);
 }
+
+function test_move_breakpoint() {
+  let bpStore = new BreakpointStore();
+
+  let oldLocation = {
+    url: "http://example.com/foo.js",
+    line: 10
+  };
+
+  let newLocation = {
+    url: "http://example.com/foo.js",
+    line: 12
+  };
+
+  bpStore.addBreakpoint(oldLocation);
+  bpStore.moveBreakpoint(oldLocation, newLocation);
+
+  equal(bpStore.size, 1, "Moving a breakpoint maintains the correct size.");
+
+  let bp = bpStore.getBreakpoint(newLocation);
+  ok(bp, "We should be able to get a breakpoint at the new location.");
+  equal(bp.line, newLocation.line,
+        "We should get the moved line.");
+
+  equal(bpStore.hasBreakpoint({ url: "http://example.com/foo.js", line: 10 }),
+        null,
+        "And we shouldn't be able to get any BP at the old location.");
+}
--- a/toolkit/modules/Promise-backend.js
+++ b/toolkit/modules/Promise-backend.js
@@ -35,16 +35,19 @@ const STATUS_REJECTED = 2;
 // properties are inaccessible by other code, but provide enough protection to
 // avoid using them by mistake.
 const salt = Math.floor(Math.random() * 100);
 const N_INTERNALS = "{private:internals:" + salt + "}";
 
 const JS_HAS_SYMBOLS = typeof Symbol === "function";
 const ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
 
+// We use DOM Promise for scheduling the walker loop.
+const DOMPromise = Promise;
+
 /////// Warn-upon-finalization mechanism
 //
 // One of the difficult problems with promises is locating uncaught
 // rejections. We adopt the following strategy: if a promise is rejected
 // at the time of its garbage-collection *and* if the promise is at the
 // end of a promise chain (i.e. |thatPromise.then| has never been
 // called), then we print a warning.
 //
@@ -680,18 +683,17 @@ this.PromiseWalker = {
   },
 
   /**
    * Sets up the PromiseWalker loop to start on the next tick of the event loop
    */
   scheduleWalkerLoop: function()
   {
     this.walkerLoopScheduled = true;
-    Services.tm.currentThread.dispatch(this.walkerLoop,
-                                       Ci.nsIThread.DISPATCH_NORMAL);
+    DOMPromise.resolve().then(() => this.walkerLoop());
   },
 
   /**
    * Schedules the resolution or rejection handlers registered on the provided
    * promise for processing.
    *
    * @param aPromise
    *        Resolved or rejected promise whose handlers should be processed.  It
--- a/toolkit/mozapps/extensions/test/browser/browser_bug562797.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562797.js
@@ -254,34 +254,36 @@ add_test(function() {
         if (event.target.location != "about:addons")
           return;
         gBrowser.removeEventListener("pageshow", arguments.callee, true);
 
         wait_for_view_load(gBrowser.contentWindow.wrappedJSObject, function(aManager) {
           info("Part 3");
           is_in_list(aManager, "addons://list/extension", true, false);
 
-          go_back(aManager);
+          executeSoon(() => go_back(aManager));
           gBrowser.addEventListener("pageshow", function() {
             gBrowser.removeEventListener("pageshow", arguments.callee, false);
             info("Part 4");
-            is(gBrowser.currentURI.spec, "http://example.com/", "Should be showing the webpage");
-            ok(!gBrowser.canGoBack, "Should not be able to go back");
-            ok(gBrowser.canGoForward, "Should be able to go forward");
+            executeSoon(() => executeSoon(function () {
+              is(gBrowser.currentURI.spec, "http://example.com/", "Should be showing the webpage");
+              ok(!gBrowser.canGoBack, "Should not be able to go back");
+              ok(gBrowser.canGoForward, "Should be able to go forward");
 
-            go_forward(aManager);
-            gBrowser.addEventListener("pageshow", function() {
-              gBrowser.removeEventListener("pageshow", arguments.callee, false);
-              wait_for_view_load(gBrowser.contentWindow.wrappedJSObject, function(aManager) {
-                info("Part 5");
-                is_in_list(aManager, "addons://list/extension", true, false);
+              go_forward(aManager);
+              gBrowser.addEventListener("pageshow", function() {
+                gBrowser.removeEventListener("pageshow", arguments.callee, false);
+                wait_for_view_load(gBrowser.contentWindow.wrappedJSObject, function(aManager) {
+                  info("Part 5");
+                  is_in_list(aManager, "addons://list/extension", true, false);
 
-                close_manager(aManager, run_next_test);
-              });
-            }, false);
+                  close_manager(aManager, run_next_test);
+                });
+              }, false);
+            }));
           }, false);
         });
       }, true);
     });
   }, false);
 });
 
 // Tests simple forward and back navigation and that the right heading and
@@ -433,17 +435,17 @@ add_test(function() {
           if (event.target.location != "about:addons")
             return;
           gBrowser.removeEventListener("pageshow", arguments.callee, false);
 
           wait_for_view_load(gBrowser.contentWindow.wrappedJSObject, function(aManager) {
             info("Part 3");
             is_in_list(aManager, "addons://list/plugin", false, true);
 
-            go_forward(aManager);
+            executeSoon(() => go_forward(aManager));
             gBrowser.addEventListener("pageshow", function(event) {
               if (event.target.location != "http://example.com/")
                 return;
               gBrowser.removeEventListener("pageshow", arguments.callee, false);
               info("Part 4");
 
               executeSoon(function() {
                 ok(gBrowser.canGoBack, "Should be able to go back");
--- a/toolkit/themes/linux/global/inContentUI.css
+++ b/toolkit/themes/linux/global/inContentUI.css
@@ -14,16 +14,23 @@
   -moz-appearance: none;
   padding: 18px;
   background-color: Window;
   background-image: /* Texture */
                     url("chrome://global/skin/inContentUI/background-texture.png");
   color: WindowText;
 }
 
+/* Use the new in-content colors for #contentAreaDownloadsView. After landing
+   of bug 989469 the colors can be moved to *|*:root */
+*|*#contentAreaDownloadsView {
+  background: #f1f1f1;
+  color: #424e5a;
+}
+
 html|html {
   font: message-box;
 }
 
 /* Content */
 *|*.main-content {
   /* Needed to allow the radius to clip the inner content, see bug 595656 */
   overflow: hidden;
--- a/toolkit/themes/osx/global/inContentUI.css
+++ b/toolkit/themes/osx/global/inContentUI.css
@@ -16,16 +16,23 @@
   -moz-appearance: none;
   padding: 18px;
   background-image: /* Texture */
                     url("chrome://global/skin/inContentUI/background-texture.png"),
                     /* Gradient */
                     linear-gradient(#ADB5C2, #BFC6D1);
 }
 
+/* Use the new in-content colors for #contentAreaDownloadsView. After landing
+   of bug 989469 the colors can be moved to *|*:root */
+*|*#contentAreaDownloadsView {
+  background: #f1f1f1;
+  color: #424e5a;
+}
+
 html|html {
   font: message-box;
 }
 
 /* Content */
 *|*.main-content {
   /* Needed to allow the radius to clip the inner content, see bug 595656 */
   overflow: hidden;
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -72,16 +72,17 @@ xul|groupbox xul|label {
 
 xul|tabpanels {
   -moz-appearance: none;
   font-size: 1.25rem;
   line-height: 22px;
   border: none;
   padding: 0;
   background-color: transparent;
+  color: inherit;
 }
 
 xul|tabs {
   margin-bottom: 15px;
   border-top: 1px solid #c1c1c1;
   border-bottom: 1px solid #c1c1c1;
   background-color: #fbfbfb;
 }
--- a/toolkit/themes/windows/global/inContentUI.css
+++ b/toolkit/themes/windows/global/inContentUI.css
@@ -55,16 +55,23 @@ html|html {
                                            /* Third light beam */
                                            transparent 87%, rgba(255,255,255,0.2) 90%),
                       /* Texture */
                       url("chrome://global/skin/inContentUI/background-texture.png");
   }
 }
 %endif
 
+/* Use the new in-content colors for #contentAreaDownloadsView. After landing
+   of bug 989469 the colors can be moved to *|*:root */
+*|*#contentAreaDownloadsView {
+  background: #f1f1f1;
+  color: #424e5a;
+}
+
 /* Content */
 *|*.main-content {
   /* Needed to allow the radius to clip the inner content, see bug 595656 */
   overflow: hidden;
   background-color: rgba(255, 255, 255, 0.35);
   background-image: linear-gradient(rgba(255, 255, 255, 0),
                                     rgba(255, 255, 255, 0.75));
   border: 1px solid #C3CEDF;
--- a/tools/profiler/GeckoTaskTracer.h
+++ b/tools/profiler/GeckoTaskTracer.h
@@ -71,17 +71,17 @@ nsTArray<nsCString>* GetLoggedData(TimeS
 /**
  * Internal functions.
  */
 
 Task* CreateTracedTask(Task* aTask);
 
 already_AddRefed<nsIRunnable> CreateTracedRunnable(nsIRunnable* aRunnable);
 
-FakeTracedTask* CreateFakeTracedTask(int* aVptr);
+already_AddRefed<FakeTracedTask> CreateFakeTracedTask(int* aVptr);
 
 // Free the TraceInfo allocated on a thread's TLS. Currently we are wrapping
 // tasks running on nsThreads and base::thread, so FreeTraceInfo is called at
 // where nsThread and base::thread release themselves.
 void FreeTraceInfo();
 
 } // namespace tasktracer
 } // namespace mozilla.
--- a/tools/profiler/TracedTaskCommon.cpp
+++ b/tools/profiler/TracedTaskCommon.cpp
@@ -157,17 +157,17 @@ CreateTracedTask(Task* aTask)
   Task* task = new TracedTask(aTask);
   return task;
 }
 
 /**
  * CreateFakeTracedTask() returns a FakeTracedTask tracking the event which is
  * not dispatched from its parent task directly, such as timer events.
  */
-FakeTracedTask*
+already_AddRefed<FakeTracedTask>
 CreateFakeTracedTask(int* aVptr)
 {
-  nsAutoPtr<FakeTracedTask> task(new FakeTracedTask(aVptr));
+  nsRefPtr<FakeTracedTask> task(new FakeTracedTask(aVptr));
   return task.forget();
 }
 
 } // namespace tasktracer
 } // namespace mozilla
--- a/tools/profiler/TracedTaskCommon.h
+++ b/tools/profiler/TracedTaskCommon.h
@@ -76,16 +76,18 @@ class FakeTracedTask : public TracedTask
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(FakeTracedTask)
 
   FakeTracedTask(int* aVptr);
   void BeginFakeTracedTask();
   void EndFakeTracedTask();
 private:
+  virtual ~FakeTracedTask() {}
+
   // No copy allowed.
   FakeTracedTask() MOZ_DELETE;
   FakeTracedTask(const FakeTracedTask& aTask) MOZ_DELETE;
   FakeTracedTask& operator=(const FakeTracedTask& aTask) MOZ_DELETE;
 };
 
 class AutoRunFakeTracedTask
 {