Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 05 Aug 2015 13:19:38 +0200
changeset 287947 eb6b06526c5d4c7259c36c2ff64865e6fc3e3866
parent 287946 0a7ace2ffb9bb466975ea09e08beb879e3328133 (current diff)
parent 287933 b12a261ee32e04d96e2e2594b3ba06979770495b (diff)
child 287948 0f4864d20e8cd84dcd3dd47a1c5840c7aecc0a39
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone42.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 mozilla-central to mozilla-inbound
browser/components/loop/content/shared/img/svg/glyph-settings-16x16.svg
browser/locales/en-US/chrome/browser/devtools/profiler.dtd
browser/locales/en-US/chrome/browser/devtools/profiler.properties
browser/locales/en-US/chrome/browser/devtools/timeline.dtd
browser/locales/en-US/chrome/browser/devtools/timeline.properties
modules/libpref/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1162,8 +1162,12 @@ pref("layers.compositor-lru-size", 10);
 // Enable Cardboard VR on mobile, assuming VR at all is enabled
 pref("dom.vr.cardboard.enabled", true);
 
 // In B2G by deafult any AudioChannelAgent is muted when created.
 pref("dom.audiochannel.mutedByDefault", true);
 
 // Default device name for Presentation API
 pref("dom.presentation.device.name", "Firefox OS");
+
+// Enable notification of performance timing
+pref("dom.performance.enable_notify_performance_timing", true);
+
--- a/b2g/chrome/content/devtools/hud.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -20,16 +20,20 @@ XPCOMUtils.defineLazyGetter(this, 'Debug
 XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
   return devtools.require('devtools/toolkit/webconsole/utils').Utils;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
   return devtools.require('devtools/server/actors/eventlooplag').EventLoopLagFront;
 });
 
+XPCOMUtils.defineLazyGetter(this, 'PerformanceEntriesFront', function() {
+  return devtools.require('devtools/server/actors/performance-entries').PerformanceEntriesFront;
+});
+
 XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
   return devtools.require('devtools/server/actors/memory').MemoryFront;
 });
 
 Cu.import('resource://gre/modules/Frames.jsm');
 
 let _telemetryDebug = true;
 
@@ -583,16 +587,86 @@ let eventLoopLagWatcher = {
     if (fronts.has(target)) {
       fronts.get(target).destroy();
       fronts.delete(target);
     }
   }
 };
 developerHUD.registerWatcher(eventLoopLagWatcher);
 
+/*
+ * The performanceEntriesWatcher determines the delta between the epoch
+ * of an app's launch time and the app's performance entry marks.
+ * When it receives an "appLaunch" performance entry mark it records the
+ * name of the app being launched and the epoch of when the launch ocurred.
+ * When it receives subsequent performance entry events for the app being
+ * launched, it records the delta of the performance entry opoch compared
+ * to the app-launch epoch and emits an "app-start-time-<performance mark name>"
+ * event containing the delta.
+ */
+let performanceEntriesWatcher = {
+  _client: null,
+  _fronts: new Map(),
+  _appLaunchName: null,
+  _appLaunchStartTime: null,
+
+  init(client) {
+    this._client = client;
+  },
+
+  trackTarget(target) {
+    // The performanceEntries watcher doesn't register a metric because
+    // currently the metrics generated are not displayed in
+    // in the front-end.
+
+    let front = new PerformanceEntriesFront(this._client, target.actor);
+    this._fronts.set(target, front);
+
+    // User timings are always gathered; there is no setting to enable/
+    // disable.
+    front.start();
+
+    front.on('entry', detail => {
+      if (detail.type === 'mark') {
+        let name = detail.name;
+        let epoch = detail.epoch;
+        let CHARS_UNTIL_APP_NAME = 7; // '@app://'
+
+        // FIXME There is a potential race condition that can result
+        // in some performance entries being disregarded. See bug 1189942.
+        if (name.indexOf('appLaunch') != -1) {
+          let appStartPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
+          let length = (name.indexOf('.') - appStartPos);
+          this._appLaunchName = name.substr(appStartPos, length);
+          this._appLaunchStartTime = epoch;
+        } else {
+          let origin = detail.origin;
+          origin = origin.substr(0, origin.indexOf('.'));
+          if (this._appLaunchName === origin) {
+            let time = epoch - this._appLaunchStartTime;
+            let eventName = 'app-startup-time-' + name;
+
+            // Events based on performance marks are for telemetry only, they are
+            // not displayed in the HUD front end.
+            target._sendTelemetryEvent({name: eventName, value: time});
+          }
+        }
+      }
+    });
+  },
+
+  untrackTarget(target) {
+    let fronts = this._fronts;
+    if (fronts.has(target)) {
+      fronts.get(target).destroy();
+      fronts.delete(target);
+    }
+  }
+};
+developerHUD.registerWatcher(performanceEntriesWatcher);
 
 /**
  * The Memory Watcher uses devtools actors to track memory usage.
  */
 let memoryWatcher = {
 
   _client: null,
   _fronts: new Map(),
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
--- 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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,21 +14,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
-  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8bc59310552179f9a8bc6cdd0188e2475df52fb7"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
+  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
   <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"/>
--- 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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="07c383a786f188904311a37f6062c2cb84c9b61d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,21 +14,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
-  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8bc59310552179f9a8bc6cdd0188e2475df52fb7"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
+  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
--- 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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
@@ -134,17 +134,17 @@
   <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
   <project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="65f5144987afff35a932262c0c5fad6ecce0c04a"/>
   <project name="platform/frameworks/base" path="frameworks/base" revision="da8e6bc53c8bc669da0bb627904d08aa293f2497"/>
   <project name="platform/frameworks/native" path="frameworks/native" revision="a46a9f1ac0ed5662d614c277cbb14eb3f332f365"/>
   <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="7196881a0e9dd7bfbbcf0af64c8064e70f0fa094"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="8d7676dfb68ee0cd069affedd5d1e97316a184ba"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
-  <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="a74adcf8d88320d936daa8d20ce88ca0107fb916"/>
+  <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="0eb5fd21d8697136ee4a0166f5e06bff25cc1e8a"/>
   <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="42839aedcf70bf6bc92a3b7ea4a5cc9bf9aef3f9"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "c5425d9f1f5184731a59ed4bc99295acbde30390", 
+        "git_revision": "581de383687dc441a878d2c91a0167c6ec688fef", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "68ce99a4e1761c06e5f31f6674ee46fef1bbf44b", 
+    "revision": "a0015e0f59bdec57f732c805238172bd49406ab5", 
     "repo_path": "integration/gaia-central"
 }
--- 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="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
   <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"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="07c383a786f188904311a37f6062c2cb84c9b61d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5425d9f1f5184731a59ed4bc99295acbde30390"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="581de383687dc441a878d2c91a0167c6ec688fef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="6ddfd98cdafefaa1b60273d5568b8dbd13730dae"/>
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -353,16 +353,18 @@ function init() {
             show("stage", "intro");
             // load the remote frame in the background
             wrapper.init(fxAccounts.getAccountsSignUpURI(), urlParams);
           }
         });
       }
       break;
     }
+  }).catch(err => {
+    error("Failed to get the signed in user: " + err);
   });
 }
 
 // Causes the "top-level" element with |id| to be shown - all other top-level
 // elements are hidden.  Optionally, ensures that only 1 "second-level" element
 // inside the top-level one is shown.
 function show(id, childId) {
   // top-level items are either <div> or <iframe>
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -28,16 +28,17 @@ let gFxAccounts = {
     delete this.topics;
     return this.topics = [
       "weave:service:ready",
       "weave:service:sync:start",
       "weave:service:login:error",
       "weave:service:setup-complete",
       "weave:ui:login:error",
       "fxa-migration:state-changed",
+      this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
       this.FxAccountsCommon.ONVERIFIED_NOTIFICATION,
       this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
       "weave:notification:removed",
       this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
     ];
   },
 
   get panelUIFooter() {
@@ -217,36 +218,37 @@ let gFxAccounts = {
     this.showDoorhanger("sync-error-panel");
   },
 
   updateUI: function () {
     this.updateAppMenuItem();
     this.updateMigrationNotification();
   },
 
+  // Note that updateAppMenuItem() returns a Promise that's only used by tests.
   updateAppMenuItem: function () {
     if (this._migrationInfo) {
       this.updateAppMenuItemForMigration();
-      return;
+      return Promise.resolve();
     }
 
     let profileInfoEnabled = false;
     try {
       profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
     } catch (e) { }
 
     // Bail out if FxA is disabled.
     if (!this.weave.fxAccountsEnabled) {
       // When migration transitions from needs-verification to the null state,
       // fxAccountsEnabled is false because migration has not yet finished.  In
       // that case, hide the button.  We'll get another notification with a null
       // state once migration is complete.
       this.panelUIFooter.hidden = true;
       this.panelUIFooter.removeAttribute("fxastatus");
-      return;
+      return Promise.resolve();
     }
 
     this.panelUIFooter.hidden = false;
 
     // Make sure the button is disabled in customization mode.
     if (this._inCustomizationMode) {
       this.panelUIStatus.setAttribute("disabled", "true");
       this.panelUILabel.setAttribute("disabled", "true");
@@ -306,33 +308,39 @@ let gFxAccounts = {
             this.panelUIFooter.setAttribute("fxaprofileimage", "set");
             this.panelUIAvatar.style.listStyleImage = "url('" + profile.avatar + "')";
           };
           img.src = profile.avatar;
         }
       }
     }
 
-    // Calling getSignedInUserProfile() without a user logged in causes log
-    // noise that looks like an actual error...
-    fxAccounts.getSignedInUser().then(userData => {
+    return fxAccounts.getSignedInUser().then(userData => {
       // userData may be null here when the user is not signed-in, but that's expected
       updateWithUserData(userData);
-      return userData ? fxAccounts.getSignedInUserProfile() : null;
+      // unverified users cause us to spew log errors fetching an OAuth token
+      // to fetch the profile, so don't even try in that case.
+      if (!userData || !userData.verified || !profileInfoEnabled) {
+        return null; // don't even try to grab the profile.
+      }
+      return fxAccounts.getSignedInUserProfile().catch(err => {
+        // Not fetching the profile is sad but the FxA logs will already have noise.
+        return null;
+      });
     }).then(profile => {
       if (!profile) {
         return;
       }
       updateWithProfile(profile);
     }).catch(error => {
       // This is most likely in tests, were we quickly log users in and out.
       // The most likely scenario is a user logged out, so reflect that.
       // Bug 995134 calls for better errors so we could retry if we were
       // sure this was the failure reason.
-      this.FxAccountsCommon.log.error("Error updating FxA profile", error);
+      this.FxAccountsCommon.log.error("Error updating FxA account info", error);
       updateWithUserData(null);
     });
   },
 
   updateAppMenuItemForMigration: Task.async(function* () {
     let status = null;
     let label = null;
     switch (this._migrationInfo.state) {
--- a/browser/base/content/browser-trackingprotection.js
+++ b/browser/base/content/browser-trackingprotection.js
@@ -24,17 +24,17 @@ let TrackingProtection = {
     Services.prefs.addObserver(this.PREF_ENABLED_GLOBALLY, this, false);
     Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this, false);
 
     this.activeTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.activeTooltip");
     this.disabledTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip");
 
-    this.enabledHistogram.add(this.enabledGlobally);
+    this.enabledHistogramAdd(this.enabledGlobally);
   },
 
   uninit() {
     Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
     Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
   },
 
   observe() {
@@ -50,22 +50,35 @@ let TrackingProtection = {
   updateEnabled() {
     this.enabledGlobally =
       Services.prefs.getBoolPref(this.PREF_ENABLED_GLOBALLY);
     this.enabledInPrivateWindows =
       Services.prefs.getBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS);
     this.container.hidden = !this.enabled;
   },
 
-  get enabledHistogram() {
-    return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED");
+  enabledHistogramAdd(value) {
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      return;
+    }
+    Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value);
   },
 
-  get eventsHistogram() {
-    return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
+  eventsHistogramAdd(value) {
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      return;
+    }
+    Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value);
+  },
+
+  shieldHistogramAdd(value) {
+    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+      return;
+    }
+    Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
   },
 
   onSecurityChange(state, isSimulated) {
     if (!this.enabled) {
       return;
     }
 
     // Only animate the shield if the event was not fired directly from
@@ -86,28 +99,35 @@ let TrackingProtection = {
 
       // Open the tracking protection introduction panel, if applicable.
       let introCount = gPrefService.getIntPref("privacy.trackingprotection.introCount");
       if (introCount < TrackingProtection.MAX_INTROS) {
         gPrefService.setIntPref("privacy.trackingprotection.introCount", ++introCount);
         gPrefService.savePrefFile(null);
         this.showIntroPanel();
       }
+
+      this.shieldHistogramAdd(2);
     } else if (isAllowing) {
       this.icon.setAttribute("tooltiptext", this.disabledTooltipText);
       this.icon.setAttribute("state", "loaded-tracking-content");
       this.content.setAttribute("state", "loaded-tracking-content");
+
+      this.shieldHistogramAdd(1);
     } else {
       this.icon.removeAttribute("tooltiptext");
       this.icon.removeAttribute("state");
       this.content.removeAttribute("state");
+
+      // We didn't show the shield
+      this.shieldHistogramAdd(0);
     }
 
     // Telemetry for state change.
-    this.eventsHistogram.add(0);
+    this.eventsHistogramAdd(0);
   },
 
   disableForCurrentPage() {
     // Convert document URI into the format used by
     // nsChannelClassifier::ShouldEnableTrackingProtection.
     // Any scheme turned into https is correct.
     let normalizedUrl = Services.io.newURI(
       "https://" + gBrowser.selectedBrowser.currentURI.hostPort,
@@ -119,17 +139,17 @@ let TrackingProtection = {
     if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
       PrivateBrowsingUtils.addToTrackingAllowlist(normalizedUrl);
     } else {
       Services.perms.add(normalizedUrl,
         "trackingprotection", Services.perms.ALLOW_ACTION);
     }
 
     // Telemetry for disable protection.
-    this.eventsHistogram.add(1);
+    this.eventsHistogramAdd(1);
 
     // Hide the control center.
     document.getElementById("identity-popup").hidePopup();
 
     BrowserReload();
   },
 
   enableForCurrentPage() {
@@ -142,17 +162,17 @@ let TrackingProtection = {
 
     if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
       PrivateBrowsingUtils.removeFromTrackingAllowlist(normalizedUrl);
     } else {
       Services.perms.remove(normalizedUrl, "trackingprotection");
     }
 
     // Telemetry for enable protection.
-    this.eventsHistogram.add(2);
+    this.eventsHistogramAdd(2);
 
     // Hide the control center.
     document.getElementById("identity-popup").hidePopup();
 
     BrowserReload();
   },
 
   showIntroPanel: Task.async(function*() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6864,20 +6864,16 @@ var gIdentityHandler = {
     // - tracking content is blocked
     // - tracking content is not blocked
     if (state &
         (nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT |
          nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT  |
          nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT     |
          nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT)) {
       this.showBadContentDoorhanger(state);
-    } else if (TrackingProtection.enabled) {
-      // We didn't show the shield
-      Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD")
-        .add(0);
     }
   },
 
   showBadContentDoorhanger : function(state) {
     var currentNotification =
       PopupNotifications.getNotification("bad-content",
         gBrowser.selectedBrowser);
 
@@ -6889,29 +6885,16 @@ var gIdentityHandler = {
       /* keep doorhanger collapsed */
       dismissed: true,
       state: state
     };
 
     // default
     let iconState = "bad-content-blocked-notification-icon";
 
-    // Telemetry for whether the shield was due to tracking protection or not
-    let histogram = Services.telemetry.getHistogramById
-                      ("TRACKING_PROTECTION_SHIELD");
-    if (state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) {
-      histogram.add(1);
-    } else if (state &
-               Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) {
-      histogram.add(2);
-    } else if (gPrefService.getBoolPref("privacy.trackingprotection.enabled")) {
-      // Tracking protection is enabled but no tracking elements are loaded,
-      // the shield is due to mixed content.
-      histogram.add(3);
-    }
     if (state &
         (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
          Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT)) {
       iconState = "bad-content-unblocked-notification-icon";
     }
 
     PopupNotifications.show(gBrowser.selectedBrowser, "bad-content",
                             "", iconState, null, null, options);
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -298,16 +298,18 @@ skip-if = e10s
 [browser_drag.js]
 skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
 [browser_favicon_change.js]
 [browser_favicon_change_not_in_document.js]
 [browser_findbarClose.js]
 [browser_focusonkeydown.js]
 [browser_fullscreen-window-open.js]
 skip-if = buildapp == 'mulet' || e10s || os == "linux" # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly. Linux: Intermittent failures - bug 941575.
+[browser_fxaccounts.js]
+support-files = fxa_profile_handler.sjs
 [browser_fxa_migrate.js]
 [browser_fxa_oauth.js]
 [browser_fxa_web_channel.js]
 [browser_gestureSupport.js]
 skip-if = e10s # Bug 863514 - no gesture support.
 [browser_getshortcutoruri.js]
 [browser_hide_removing.js]
 [browser_homeDrop.js]
@@ -433,16 +435,20 @@ tags = trackingprotection
 tags = trackingprotection
 support-files =
   trackingPage.html
   benignPage.html
 [browser_trackingUI_5.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
+[browser_trackingUI_telemetry.js]
+tags = trackingprotection
+support-files =
+  trackingPage.html
 [browser_typeAheadFind.js]
 skip-if = buildapp == 'mulet'
 [browser_unknownContentType_title.js]
 [browser_unloaddialogs.js]
 skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
 [browser_urlHighlight.js]
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxaccounts.js
@@ -0,0 +1,258 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
+let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+let {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
+let FxAccountsCommon = {};
+Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
+
+const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
+
+// instrument gFxAccounts to send observer notifications when it's done
+// what it does.
+(function() {
+  let unstubs = {}; // The original functions we stub out.
+
+  // The stub functions.
+  let stubs = {
+    updateAppMenuItem: function() {
+      return unstubs['updateAppMenuItem'].call(gFxAccounts).then(() => {
+        Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateAppMenuItem", null);
+      });
+    },
+    // Opening preferences is trickier than it should be as leaks are reported
+    // due to the promises it fires off at load time  and there's no clear way to
+    // know when they are done.
+    // So just ensure openPreferences is called rather than whether it opens.
+    openPreferences: function() {
+      Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences", null);
+    }
+  };
+
+  for (let name in stubs) {
+    unstubs[name] = gFxAccounts[name];
+    gFxAccounts[name] = stubs[name];
+  }
+  // and undo our damage at the end.
+  registerCleanupFunction(() => {
+    for (let name in unstubs) {
+      gFxAccounts[name] = unstubs[name];
+    }
+    stubs = unstubs = null;
+  });
+})();
+
+// Other setup/cleanup
+let newTab;
+
+Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
+                           TEST_ROOT + "accounts_testRemoteCommands.html");
+
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
+  Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri");
+  gBrowser.removeTab(newTab);
+});
+
+add_task(function* initialize() {
+  // Set a new tab with something other than about:blank, so it doesn't get reused.
+  // We must wait for it to load or the promiseTabOpen() call in the next test
+  // gets confused.
+  newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
+  yield promiseTabLoaded(newTab);
+});
+
+// The elements we care about.
+let panelUILabel = document.getElementById("PanelUI-fxa-label");
+let panelUIStatus = document.getElementById("PanelUI-fxa-status");
+let panelUIFooter = document.getElementById("PanelUI-footer-fxa");
+
+// The tests
+add_task(function* test_nouser() {
+  let user = yield fxAccounts.getSignedInUser();
+  Assert.strictEqual(user, null, "start with no user signed in");
+  let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+  Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, null);
+  yield promiseUpdateDone;
+
+  // Check the world - the FxA footer area is visible as it is offering a signin.
+  Assert.ok(isFooterVisible())
+
+  Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel"));
+  Assert.ok(!panelUIStatus.hasAttribute("tooltiptext"), "no tooltip when signed out");
+  Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out");
+  Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out");
+
+  let promiseOpen = promiseTabOpen("about:accounts?entryPoint=menupanel");
+  panelUIStatus.click();
+  yield promiseOpen;
+});
+
+/*
+XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds.
+
+add_task(function* test_unverifiedUser() {
+  let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+  yield setSignedInUser(false); // this will fire the observer that does the update.
+  yield promiseUpdateDone;
+
+  // Check the world.
+  Assert.ok(isFooterVisible())
+
+  Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+  Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+               panelUIStatus.getAttribute("signedinTooltiptext"));
+  Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+  let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+  panelUIStatus.click();
+  yield promisePreferencesOpened
+  yield signOut();
+});
+*/
+
+add_task(function* test_verifiedUserEmptyProfile() {
+  // We see 2 updateAppMenuItem() calls - one for the signedInUser and one after
+  // we first fetch the profile. We want them both to fire or we aren't testing
+  // the state we think we are testing.
+  let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
+  configureProfileURL({}); // successful but empty profile.
+  yield setSignedInUser(true); // this will fire the observer that does the update.
+  yield promiseUpdateDone;
+
+  // Check the world.
+  Assert.ok(isFooterVisible())
+  Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+  Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+               panelUIStatus.getAttribute("signedinTooltiptext"));
+  Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+
+  let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+  panelUIStatus.click();
+  yield promisePreferencesOpened;
+  yield signOut();
+});
+
+add_task(function* test_verifiedUserDisplayName() {
+  let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
+  configureProfileURL({ displayName: "Test User Display Name" });
+  yield setSignedInUser(true); // this will fire the observer that does the update.
+  yield promiseUpdateDone;
+
+  Assert.ok(isFooterVisible())
+  Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
+  Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+               panelUIStatus.getAttribute("signedinTooltiptext"));
+  Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+  yield signOut();
+});
+
+add_task(function* test_verifiedUserProfileFailure() {
+  // profile failure means only one observer fires.
+  let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 1);
+  configureProfileURL(null, 500);
+  yield setSignedInUser(true); // this will fire the observer that does the update.
+  yield promiseUpdateDone;
+
+  Assert.ok(isFooterVisible())
+  Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+  Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+               panelUIStatus.getAttribute("signedinTooltiptext"));
+  Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+  yield signOut();
+});
+
+// Helpers.
+function isFooterVisible() {
+  let style = window.getComputedStyle(panelUIFooter);
+  return style.getPropertyValue("display") == "flex";
+}
+
+function configureProfileURL(profile, responseStatus = 200) {
+  let responseBody = profile ? JSON.stringify(profile) : "";
+  let url = TEST_ROOT + "fxa_profile_handler.sjs?" +
+            "responseStatus=" + responseStatus +
+            "responseBody=" + responseBody +
+            // This is a bit cheeky - the FxA code will just append "/profile"
+            // to the preference value. We arrange for this to be seen by our
+            //.sjs as part of the query string.
+            "&path=";
+
+  Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url);
+}
+
+function promiseObserver(topic, count = 1) {
+  return new Promise(resolve => {
+    let obs = (subject, topic, data) => {
+      if (--count == 0) {
+        Services.obs.removeObserver(obs, topic);
+        resolve(subject);
+      }
+    }
+    Services.obs.addObserver(obs, topic, false);
+  });
+}
+
+// Stolen from browser_aboutHome.js
+function promiseWaitForEvent(node, type, capturing) {
+  return new Promise((resolve) => {
+    node.addEventListener(type, function listener(event) {
+      node.removeEventListener(type, listener, capturing);
+      resolve(event);
+    }, capturing);
+  });
+}
+
+let promiseTabOpen = Task.async(function*(urlBase) {
+  info("Waiting for tab to open...");
+  let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
+  let tab = event.target;
+  yield promiseTabLoadEvent(tab);
+  ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase),
+     "Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase);
+  let whenUnloaded = promiseTabUnloaded(tab);
+  gBrowser.removeTab(tab);
+  yield whenUnloaded;
+});
+
+function promiseTabUnloaded(tab)
+{
+  return new Promise(resolve => {
+    info("Wait for tab to unload");
+    function handle(event) {
+      tab.linkedBrowser.removeEventListener("unload", handle, true);
+      info("Got unload event");
+      resolve(event);
+    }
+    tab.linkedBrowser.addEventListener("unload", handle, true, true);
+  });
+}
+
+// FxAccounts helpers.
+function setSignedInUser(verified) {
+  let data = {
+    email: "foo@example.com",
+    uid: "1234@lcip.org",
+    assertion: "foobar",
+    sessionToken: "dead",
+    kA: "beef",
+    kB: "cafe",
+    verified: verified,
+
+    oauthTokens: {
+      // a token for the profile server.
+      profile: "key value",
+    }
+  }
+  return fxAccounts.setSignedInUser(data);
+}
+
+let signOut = Task.async(function* () {
+  // This test needs to make sure that any updates for the logout have
+  // completed before starting the next test, or we see the observer
+  // notifications get out of sync.
+  let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+  // we always want a "localOnly" signout here...
+  yield fxAccounts.signOut(true);
+  yield promiseUpdateDone;
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_telemetry.js
@@ -0,0 +1,145 @@
+/*
+ * Test telemetry for Tracking Protection
+ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+/**
+ * Enable local telemetry recording for the duration of the tests.
+ */
+let oldCanRecord = Services.telemetry.canRecordExtended;
+Services.telemetry.canRecordExtended = true;
+Services.prefs.setBoolPref(PREF, false);
+Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").clear();
+registerCleanupFunction(function () {
+  UrlClassifierTestUtils.cleanupTestTrackers();
+  Services.telemetry.canRecordExtended = oldCanRecord;
+  Services.prefs.clearUserPref(PREF);
+});
+
+function getShieldHistogram() {
+  return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD");
+}
+
+function getEnabledHistogram() {
+  return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED");
+}
+
+function getEventsHistogram() {
+  return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
+}
+
+function getShieldCounts() {
+  return getShieldHistogram().snapshot().counts;
+}
+
+function getEnabledCounts() {
+  return getEnabledHistogram().snapshot().counts;
+}
+
+function getEventCounts() {
+  return getEventsHistogram().snapshot().counts;
+}
+
+add_task(function* setup() {
+  yield UrlClassifierTestUtils.addTestTrackers();
+
+  let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+  ok(TrackingProtection, "TP is attached to the browser window");
+  ok(!TrackingProtection.enabled, "TP is not enabled");
+
+  // Open a window with TP disabled to make sure 'enabled' is logged correctly.
+  let newWin = yield promiseOpenAndLoadWindow({}, true);
+  yield promiseWindowClosed(newWin);
+
+  is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
+  is(getEnabledCounts()[1], 0, "TP was not enabled on start up");
+
+  // Enable TP so the next browser to open will log 'enabled'
+  Services.prefs.setBoolPref(PREF, true);
+});
+
+
+add_task(function* testNewWindow() {
+  let newWin = yield promiseOpenAndLoadWindow({}, true);
+  let tab = newWin.gBrowser.selectedTab = newWin.gBrowser.addTab();
+  let TrackingProtection = newWin.TrackingProtection;
+  ok(TrackingProtection, "TP is attached to the browser window");
+
+  is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
+  is(getEnabledCounts()[1], 1, "TP was enabled once on start up");
+
+  // Reset these to make counting easier
+  getEventsHistogram().clear();
+  getShieldHistogram().clear();
+
+  yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+  is(getEventCounts()[0], 1, "Total page loads");
+  is(getEventCounts()[1], 0, "Disable actions");
+  is(getEventCounts()[2], 0, "Enable actions");
+  is(getShieldCounts()[0], 1, "Page loads without tracking");
+
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+  // Note that right now the events and shield histogram is not measuring what
+  // you might think.  Since onSecurityChange fires twice for a tracking page,
+  // the total page loads count is double counting, and the shield count
+  // (which is meant to measure times when the shield wasn't shown) fires even
+  // when tracking elements exist on the page.
+  todo_is(getEventCounts()[0], 2, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+  is(getEventCounts()[1], 0, "Disable actions");
+  is(getEventCounts()[2], 0, "Enable actions");
+  todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+  info("Disable TP for the page (which reloads the page)");
+  let tabReloadPromise = promiseTabLoadEvent(tab);
+  newWin.document.querySelector("#tracking-action-unblock").doCommand();
+  yield tabReloadPromise;
+  todo_is(getEventCounts()[0], 3, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+  is(getEventCounts()[1], 1, "Disable actions");
+  is(getEventCounts()[2], 0, "Enable actions");
+  todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+  info("Re-enable TP for the page (which reloads the page)");
+  tabReloadPromise = promiseTabLoadEvent(tab);
+  newWin.document.querySelector("#tracking-action-block").doCommand();
+  yield tabReloadPromise;
+  todo_is(getEventCounts()[0], 4, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+  is(getEventCounts()[1], 1, "Disable actions");
+  is(getEventCounts()[2], 1, "Enable actions");
+  todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+  yield promiseWindowClosed(newWin);
+
+  // Reset these to make counting easier for the next test
+  getEventsHistogram().clear();
+  getShieldHistogram().clear();
+  getEnabledHistogram().clear();
+});
+
+add_task(function* testPrivateBrowsing() {
+  let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+  let tab = privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab();
+  let TrackingProtection = privateWin.TrackingProtection;
+  ok(TrackingProtection, "TP is attached to the browser window");
+
+  // Do a bunch of actions and make sure that no telemetry data is gathered
+  yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+  yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+  let tabReloadPromise = promiseTabLoadEvent(tab);
+  privateWin.document.querySelector("#tracking-action-unblock").doCommand();
+  yield tabReloadPromise;
+  tabReloadPromise = promiseTabLoadEvent(tab);
+  privateWin.document.querySelector("#tracking-action-block").doCommand();
+  yield tabReloadPromise;
+
+  // Sum up all the counts to make sure that nothing got logged
+  is(getEnabledCounts().reduce((p,c)=>p+c), 0, "Telemetry logging off in PB mode");
+  is(getEventCounts().reduce((p,c)=>p+c), 0, "Telemetry logging off in PB mode");
+  is(getShieldCounts().reduce((p,c)=>p+c), 0, "Telemetry logging off in PB mode");
+
+  yield promiseWindowClosed(privateWin);
+});
--- a/browser/base/content/test/general/dummy_page.html
+++ b/browser/base/content/test/general/dummy_page.html
@@ -1,8 +1,9 @@
 <html>
 <head>
 <title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
 </head>
 <body>
 <p>Dummy test page</p>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/fxa_profile_handler.sjs
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is basically an echo server!
+// We just grab responseStatus and responseBody query params!
+
+function reallyHandleRequest(request, response) {
+  var query = "?" + request.queryString;
+
+  var responseStatus = 200;
+  var match = /responseStatus=([^&]*)/.exec(query);
+  if (match) {
+    responseStatus = parseInt(match[1]);
+  }
+
+  var responseBody = "";
+  match = /responseBody=([^&]*)/.exec(query);
+  if (match) {
+    responseBody = decodeURIComponent(match[1]);
+  }
+
+  response.setStatusLine("1.0", responseStatus, "OK");
+  response.write(responseBody);
+}
+
+function handleRequest(request, response)
+{
+  try {
+    reallyHandleRequest(request, response);
+  } catch (e) {
+    response.setStatusLine("1.0", 500, "NotOK");
+    response.write("Error handling request: " + e);
+  }
+}
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -7,16 +7,17 @@
   "ecmaFeatures": {
     "forOf": true,
     "jsx": true,
   },
   "env": {
     "browser": true,
     "mocha": true
   },
+  "extends": "eslint:recommended",
   "globals": {
     "_": false,
     "$": false,
     "Backbone": false,
     "chai": false,
     "console": false,
     "jQuery": false,
     "loop": true,
@@ -30,48 +31,93 @@
   },
   "rules": {
     // turn off all kinds of stuff that we actually do want, because
     // right now, we're bootstrapping the linting infrastructure.  We'll
     // want to audit these rules, and start turning them on and fixing the
     // problems they find, one at a time.
 
     // Eslint built-in rules are documented at <http://eslint.org/docs/rules/>
-    "camelcase": 0,               // TODO: Remove (use default)
+    "callback-return": 0,         // TBD
+    "camelcase": 0,               // TODO: set to 2
+    "comma-spacing": 2,
     "computed-property-spacing": [2, "never"],
-    "consistent-return": 0,       // TODO: Remove (use default)
+    "consistent-return": 0,       // TODO: set to 2
+    "curly": [2, "all"],
     dot-location: 0,              // [2, property],
+    "eol-last": 2,
     "eqeqeq": 0,                  // TBD. Might need to be separate for content & chrome
-    "global-strict": 0,           // Leave as zero (this will be unsupported in eslint 1.0.0)
+    "key-spacing": [2, {"beforeColon": false, "afterColon": true }],
     "linebreak-style": [2, "unix"],
-    "new-cap": 0,                 // TODO: Remove (use default)
-    "no-catch-shadow": 0,         // TODO: Remove (use default)
+    "new-cap": 0,                 // TODO: set to 2
+    "new-parens": 2,
+    "no-alert": 2,
+    "no-array-constructor": 2,
+    "no-caller": 2,
+    "no-catch-shadow": 0,         // TODO: set to 2
+    "no-class-assign": 2,
+    "no-const-assign": 2,
     "no-console": 0,              // Leave as 0. We use console logging in content code.
-    "no-empty": 0,                // TODO: Remove (use default)
+    "no-empty": 0,                // TODO: set to 2
+    "no-empty-label": 2,
+    "no-eval": 2,
+    "no-extend-native": 2, // XXX
     "no-extra-bind": 0,           // Leave as 0
+    "no-extra-parens": 0,         // TODO: (bug?) [2, "functions"],
+    "no-implied-eval": 2,
+    "no-invalid-this": 0,         // TBD
+    "no-iterator": 2,
+    "no-label-var": 2,
+    "no-labels": 2,
+    "no-lone-blocks": 2,
+    "no-loop-func": 2,
     "no-multi-spaces": 0,         // TBD.
-    "no-new": 0,                  // TODO: Remove (use default)
-    "no-return-assign": 0,        // TODO: Remove (use default)
+    "no-multi-str": 2,
+    "no-native-reassign": 2,
+    "no-new": 2,
+    "no-new-func": 2,
+    "no-new-object": 2,
+    "no-new-wrappers": 2,
+    "no-octal-escape": 2,
+    "no-process-exit": 2,
+    "no-proto": 2,
+    "no-return-assign": 2,
+    "no-script-url": 2,
+    "no-sequences": 2,
+    "no-shadow": 2,
+    "no-shadow-restricted-names": 2,
+    "no-spaced-func": 2,
+    "no-trailing-spaces": 2,
+    "no-undef-init": 2,
     "no-underscore-dangle": 0,    // Leave as 0. Commonly used for private variables.
     "no-unexpected-multiline": 2,
     "no-unneeded-ternary": 2,
-    "no-unused-expressions": 0,   // TODO: Remove (use default)
-    "no-unused-vars": 0,          // TODO: Remove (use default)
-    "no-use-before-define": 0,    // TODO: Remove (use default)
+    "no-unused-expressions": 0,   // TODO: Set to 2
+    "no-unused-vars": 0,          // TODO: Set to 2
+    "no-use-before-define": 0,    // TODO: Set to 2
+    "no-useless-call": 2,
+    "no-with": 2,
     "object-curly-spacing": 0,    // [2, "always"],
     "quotes": [2, "double", "avoid-escape"],
+    "semi": 2,
+    "semi-spacing": [2, {"before": false, "after": true}],
+    "space-infix-ops": 2,
+    "space-return-throw-case": 2,
+    "space-unary-ops": [2, {"words": true, "nonwords": false}],
     "spaced-comment": [2, "always"],
     "strict": [2, "function"],
+    "yoda": [2, "never"],
     // eslint-plugin-react rules. These are documented at
     // <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
     "react/jsx-quotes": [2, "double", "avoid-escape"],
     "react/jsx-no-undef": 2,
     "react/jsx-sort-props": 2,
     "react/jsx-sort-prop-types": 2,
     "react/jsx-uses-vars": 2,
+    "react/jsx-no-duplicate-props": 2,
     // Need to fix the couple of instances which don't
     // currently pass this rule.
     "react/no-did-mount-set-state": 0,
     "react/no-did-update-set-state": 2,
     "react/no-unknown-property": 2,
     "react/prop-types": 2,
     "react/self-closing-comp": 2,
     "react/wrap-multilines": 2,
--- a/browser/components/loop/.eslintrc-gecko
+++ b/browser/components/loop/.eslintrc-gecko
@@ -51,14 +51,17 @@
     "Task": false,
     "UITour": false,
     "XPCOMUtils": false,
     "uuidgen": true,
     // Test Related
     "Assert": false,
   },
   "rules": {
+    "arrow-parens": 0, // TBD
+    "arrow-spacing": 2,
     "generator-star-spacing": [2, "after"],
     // We should fix the errors and enable this (set to 2)
     "no-var": 0,
+    "require-yield": 0,        // TODO: Set to 2.
     "strict": [2, "global"]
   }
 }
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -278,8 +278,36 @@ html[dir="rtl"] .contacts-gravatar-promo
   top: 8px;
   right: 8px;
 }
 
 html[dir="rtl"] .contacts-gravatar-promo > .button-close {
   right: auto;
   left: 8px;
 }
+
+.contact-controls {
+  padding: 0 16px;
+}
+
+.contact-controls > .button {
+  padding: .5em;
+  border: none;
+  border-radius: 5px;
+}
+
+.button.primary {
+  background: #00A9DC;
+  color: #fff;
+}
+
+.button.secondary {
+  background: #EBEBEB;
+  color: #4D4D4D;
+}
+
+.contact-controls > .primary {
+  flex: 5;
+}
+
+.contact-controls > .secondary {
+  flex: 3;
+}
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -430,20 +430,17 @@ body {
 .button {
   padding: 2px 5px;
   background-color: #fbfbfb;
   color: #333;
   border: 1px solid #c1c1c1;
   border-radius: 2px;
   min-height: 26px;
   font-size: 1.2rem;
-}
-
-.button > .button-caption {
-  vertical-align: middle;
+  line-height: 1.2rem;
 }
 
 .button:hover {
   background-color: #ebebeb;
 }
 
 .button:hover:active {
   background-color: #ccc;
@@ -636,154 +633,161 @@ html[dir="rtl"] .generate-url-spinner {
 
 .dnd-status {
   border: 1px solid transparent;
   padding: 2px 4px;
   margin: 0;
   /* Undo the start border + padding so that unhovered dnd-status is aligned
      as if there was no additional spacing. */
   -moz-margin-start: calc(-1px + -4px);
-  font-size: .9em;
   cursor: pointer;
   border-radius: 3px;
 }
 
 .dnd-status:hover {
   border-color: #ddd;
   background-color: #f1f1f1;
 }
 
+.status-available:before,
+.status-unavailable:before {
+  content: "";
+  display: inline-block;
+  width: 16px;
+  height: 16px;
+  vertical-align: bottom;
+  background-repeat: no-repeat;
+  background-size: cover;
+  -moz-margin-end: .2rem;
+  margin-bottom: -2px;
+}
+
+html[dir="rtl"] .dropdown-menu-item.status-available:before,
+html[dir="rtl"] .dropdown-menu-item.status-unavailable:before {
+  margin-right: -3px;
+}
+
+.status-available:before {
+  background-image: url("../shared/img/icons-16x16.svg#status-available");
+}
+
+.status-unavailable:before {
+  background-image: url("../shared/img/icons-16x16.svg#status-unavailable");
+}
+
 /* Status badges -- Available/Unavailable */
-
 .status {
   display: inline-block;
   width: 8px;
   height: 8px;
   margin: 0 5px;
   border-radius: 50%;
 }
 
-.status-available {
-  background-color: #6cb23e;
-}
-
-.status-dnd {
-  border: 1px solid #888;
-}
-
 /* Sign in/up link */
 
 .signin-link {
   flex: 2 1 auto;
   margin: 0;
   text-align: right;
 }
 
-.signin-link a {
-  font-size: .9em;
+.signin-link > a {
+  font-weight: 500;
   text-decoration: none;
-  color: #888;
-}
-
-.footer-signin-separator {
-  border-right: 1px solid #aaa;
-  height: 16px;
-  margin: 0 1em;
+  color: #00A9DC;
 }
 
 /* Settings (gear) menu */
 
 .button-settings {
-  display: inline-block;
-  overflow: hidden;
+  width: 10px;
+  height: 10px;
   margin: 0;
   padding: 0;
   border: none;
-  background-color: #a5a;
-  color: #fff;
-  text-align: center;
-  text-decoration: none;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  font-size: .9em;
   cursor: pointer;
-  background: transparent url(../shared/img/svg/glyph-settings-16x16.svg) no-repeat center center;
-  background-size: contain;
-  width: 12px;
-  height: 12px;
+  vertical-align: middle;
+  background: transparent url("../shared/img/icons-10x10.svg#settings-cog");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  -moz-margin-start: .5em;
 }
 
-.footer .button-settings {
-  opacity: .6;      /* used to "grey" the icon a little */
+.user-details .dropdown-menu {
+  bottom: 1.3rem; /* Just above the text. */
+  left: -5px; /* Compensate for button padding. */
+}
+
+html[dir="rtl"] .user-details .dropdown-menu {
+  right: -5px;
 }
 
 .settings-menu .dropdown-menu {
   /* The panel can't have dropdown menu overflowing its iframe boudaries;
      let's anchor it from the bottom-right, while resetting the top & left values
      set by .dropdown-menu */
   top: auto;
   left: auto;
-  bottom: -8px;
+  bottom: 1.1em;
   right: 14px;
 }
 
 html[dir="rtl"] .settings-menu .dropdown-menu {
   /* This is specified separately rather than using -moz-margin-start etc, as
      we need to override .dropdown-menu's values which can't use the gecko
      specific extensions. */
   left: 14px;
   right: auto;
 }
 
 .settings-menu .icon {
   background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
   width: 12px;
   height: 12px;
   -moz-margin-end: 1em;
   margin-top: 2px;
 }
 
-.settings-menu .icon-settings {
-  background: transparent url(../shared/img/svg/glyph-settings-16x16.svg) no-repeat center center;
-}
-
 .settings-menu .icon-tour {
-  background: transparent url("../shared/img/icons-16x16.svg#tour") no-repeat center center;
+  background-image: url("../shared/img/icons-16x16.svg#tour");
 }
 
 .settings-menu .icon-account {
-  background: transparent url(../shared/img/svg/glyph-account-16x16.svg) no-repeat center center;
+  background-image: url(../shared/img/svg/glyph-account-16x16.svg);
 }
 
 .settings-menu .icon-signin {
-  background: transparent url(../shared/img/svg/glyph-signin-16x16.svg) no-repeat center center;
+  background-image: url(../shared/img/svg/glyph-signin-16x16.svg);
 }
 
 .settings-menu .icon-signout {
-  background: transparent url(../shared/img/svg/glyph-signout-16x16.svg) no-repeat center center;
+  background-image: url(../shared/img/svg/glyph-signout-16x16.svg);
 }
 
 .settings-menu .icon-help {
-  background: transparent url(../shared/img/svg/glyph-help-16x16.svg) no-repeat center center;
+  background-image: url(../shared/img/svg/glyph-help-16x16.svg);
 }
 
 /* Footer */
 
 .footer {
   display: flex;
   flex-direction: row;
   flex-wrap: nowrap;
   justify-content: space-between;
   align-content: stretch;
   align-items: center;
-  font-size: 1em;
-  border-top: 1px solid #D1D1D1;
-  background-color: #eaeaea;
-  color: #7f7f7f;
-  padding: .5rem 1rem;
+  font-size: 1rem;
+  background-color: #fff;
+  color: #666666;
+  padding: .5rem 15px;
 }
 
 .footer .signin-details {
   align-items: center;
   display: flex;
 }
 
 .footer .user-identity {
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -585,29 +585,16 @@ loop.contacts = (function(_, mozL10n) {
             shownContacts.blocked = shownContacts.blocked.filter(filterFn);
           }
         }
       }
 
       return (
         React.createElement("div", null, 
           React.createElement("div", {className: "content-area"}, 
-            React.createElement(ButtonGroup, null, 
-              React.createElement(Button, {caption: this.state.importBusy
-                               ? mozL10n.get("importing_contacts_progress_button")
-                               : mozL10n.get("import_contacts_button2"), 
-                      disabled: this.state.importBusy, 
-                      onClick: this.handleImportButtonClick}, 
-                React.createElement("div", {className: cx({"contact-import-spinner": true,
-                                    spinner: true,
-                                    busy: this.state.importBusy})})
-              ), 
-              React.createElement(Button, {caption: mozL10n.get("new_contact_button"), 
-                      onClick: this.handleAddContactButtonClick})
-            ), 
             showFilter ?
             React.createElement("input", {className: "contact-filter", 
                    placeholder: mozL10n.get("contacts_search_placesholder"), 
                    valueLink: this.linkState("filter")})
             : null, 
             React.createElement(GravatarPromo, {handleUse: this.handleUseGravatar})
           ), 
           React.createElement("ul", {className: "contact-list"}, 
@@ -615,16 +602,31 @@ loop.contacts = (function(_, mozL10n) {
               shownContacts.available.sort(this.sortContacts).map(viewForItem) :
               null, 
             shownContacts.blocked && shownContacts.blocked.length > 0 ?
               React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
               null, 
             shownContacts.blocked ?
               shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
               null
+          ), 
+          React.createElement(ButtonGroup, {additionalClass: "contact-controls"}, 
+            React.createElement(Button, {additionalClass: "secondary", 
+                    caption: this.state.importBusy
+                             ? mozL10n.get("importing_contacts_progress_button")
+                             : mozL10n.get("import_contacts_button3"), 
+                    disabled: this.state.importBusy, 
+                    onClick: this.handleImportButtonClick}, 
+              React.createElement("div", {className: cx({"contact-import-spinner": true,
+                                  spinner: true,
+                                  busy: this.state.importBusy})})
+            ), 
+            React.createElement(Button, {additionalClass: "primary", 
+                    caption: mozL10n.get("new_contact_button"), 
+                    onClick: this.handleAddContactButtonClick})
           )
         )
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({displayName: "ContactDetailsForm",
     mixins: [React.addons.LinkedStateMixin],
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -585,29 +585,16 @@ loop.contacts = (function(_, mozL10n) {
             shownContacts.blocked = shownContacts.blocked.filter(filterFn);
           }
         }
       }
 
       return (
         <div>
           <div className="content-area">
-            <ButtonGroup>
-              <Button caption={this.state.importBusy
-                               ? mozL10n.get("importing_contacts_progress_button")
-                               : mozL10n.get("import_contacts_button2")}
-                      disabled={this.state.importBusy}
-                      onClick={this.handleImportButtonClick}>
-                <div className={cx({"contact-import-spinner": true,
-                                    spinner: true,
-                                    busy: this.state.importBusy})} />
-              </Button>
-              <Button caption={mozL10n.get("new_contact_button")}
-                      onClick={this.handleAddContactButtonClick} />
-            </ButtonGroup>
             {showFilter ?
             <input className="contact-filter"
                    placeholder={mozL10n.get("contacts_search_placesholder")}
                    valueLink={this.linkState("filter")} />
             : null }
             <GravatarPromo handleUse={this.handleUseGravatar}/>
           </div>
           <ul className="contact-list">
@@ -616,16 +603,31 @@ loop.contacts = (function(_, mozL10n) {
               null}
             {shownContacts.blocked && shownContacts.blocked.length > 0 ?
               <div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
               null}
             {shownContacts.blocked ?
               shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
               null}
           </ul>
+          <ButtonGroup additionalClass="contact-controls">
+            <Button additionalClass="secondary"
+                    caption={this.state.importBusy
+                             ? mozL10n.get("importing_contacts_progress_button")
+                             : mozL10n.get("import_contacts_button3")}
+                    disabled={this.state.importBusy}
+                    onClick={this.handleImportButtonClick} >
+              <div className={cx({"contact-import-spinner": true,
+                                  spinner: true,
+                                  busy: this.state.importBusy})} />
+            </Button>
+            <Button additionalClass="primary"
+                    caption={mozL10n.get("new_contact_button")}
+                    onClick={this.handleAddContactButtonClick} />
+          </ButtonGroup>
         </div>
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
 
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -138,46 +138,45 @@ loop.panel = (function(_, mozL10n) {
             navigator.mozLoop.doNotDisturb = true;
             break;
         }
         this.hideDropdownMenu();
       }.bind(this);
     },
 
     render: function() {
-      // XXX https://github.com/facebook/react/issues/310 for === htmlFor
       var cx = React.addons.classSet;
-      var availabilityStatus = cx({
-        "status": true,
-        "status-dnd": this.state.doNotDisturb,
-        "status-available": !this.state.doNotDisturb
-      });
       var availabilityDropdown = cx({
         "dropdown-menu": true,
         "hide": !this.state.showMenu
       });
+      var statusIcon = cx({
+        "status-unavailable": this.state.doNotDisturb,
+        "status-available": !this.state.doNotDisturb
+      });
       var availabilityText = this.state.doNotDisturb ?
-                              mozL10n.get("display_name_dnd_status") :
-                              mozL10n.get("display_name_available_status");
+                             mozL10n.get("display_name_dnd_status") :
+                             mozL10n.get("display_name_available_status");
 
       return (
         React.createElement("div", {className: "dropdown"}, 
-          React.createElement("p", {className: "dnd-status", onClick: this.toggleDropdownMenu, ref: "menu-button"}, 
-            React.createElement("span", null, availabilityText), 
-            React.createElement("i", {className: availabilityStatus})
+          React.createElement("p", {className: "dnd-status"}, 
+            React.createElement("span", {className: statusIcon, 
+                  onClick: this.toggleDropdownMenu, 
+                  ref: "menu-button"}, 
+              availabilityText
+            )
           ), 
           React.createElement("ul", {className: availabilityDropdown}, 
-            React.createElement("li", {className: "dropdown-menu-item dnd-make-available", 
+            React.createElement("li", {className: "dropdown-menu-item status-available", 
                 onClick: this.changeAvailability("available")}, 
-              React.createElement("i", {className: "status status-available"}), 
               React.createElement("span", null, mozL10n.get("display_name_available_status"))
             ), 
-            React.createElement("li", {className: "dropdown-menu-item dnd-make-unavailable", 
+            React.createElement("li", {className: "dropdown-menu-item status-unavailable", 
                 onClick: this.changeAvailability("do-not-disturb")}, 
-              React.createElement("i", {className: "status status-dnd"}), 
               React.createElement("span", null, mozL10n.get("display_name_dnd_status"))
             )
           )
         )
       );
     }
   });
 
@@ -404,17 +403,17 @@ loop.panel = (function(_, mozL10n) {
       this.closeWindow();
     },
 
     render: function() {
       var cx = React.addons.classSet;
 
       return (
         React.createElement("div", {className: "settings-menu dropdown"}, 
-          React.createElement("a", {className: "button-settings", 
+          React.createElement("button", {className: "button-settings", 
              onClick: this.toggleDropdownMenu, 
              ref: "menu-button", 
              title: mozL10n.get("settings_menu_button_tooltip")}), 
           React.createElement("ul", {className: cx({"dropdown-menu": true, hide: !this.state.showMenu})}, 
             React.createElement(SettingsDropdownEntry, {displayed: false, 
                                    icon: "settings", 
                                    label: mozL10n.get("settings_menu_item_settings"), 
                                    onClick: this.handleClickSettingsEntry}), 
@@ -438,55 +437,52 @@ loop.panel = (function(_, mozL10n) {
         )
       );
     }
   });
 
   /**
    * FxA sign in/up link component.
    */
-  var AuthLink = React.createClass({displayName: "AuthLink",
+  var AccountLink = React.createClass({displayName: "AccountLink",
     mixins: [sharedMixins.WindowCloseMixin],
 
-    handleSignUpLinkClick: function() {
+    propTypes: {
+      fxAEnabled: React.PropTypes.bool.isRequired,
+      userProfile: userProfileValidator
+    },
+
+    handleSignInLinkClick: function() {
       navigator.mozLoop.logInToFxA();
       this.closeWindow();
     },
 
     render: function() {
-      if (!navigator.mozLoop.fxAEnabled || navigator.mozLoop.userProfile) {
+      if (!this.props.fxAEnabled) {
         return null;
       }
+
+      if (this.props.userProfile && this.props.userProfile.email) {
+        return (
+          React.createElement("div", {className: "user-identity"}, 
+            loop.shared.utils.truncate(this.props.userProfile.email, 24)
+          )
+        );
+      }
+
       return (
         React.createElement("p", {className: "signin-link"}, 
-          React.createElement("a", {href: "#", onClick: this.handleSignUpLinkClick}, 
+          React.createElement("a", {href: "#", onClick: this.handleSignInLinkClick}, 
             mozL10n.get("panel_footer_signin_or_signup_link")
           )
         )
       );
     }
   });
 
-  /**
-   * FxA user identity (guest/authenticated) component.
-   */
-  var UserIdentity = React.createClass({displayName: "UserIdentity",
-    propTypes: {
-      displayName: React.PropTypes.string.isRequired
-    },
-
-    render: function() {
-      return (
-        React.createElement("p", {className: "user-identity"}, 
-          this.props.displayName
-        )
-      );
-    }
-  });
-
   var RoomEntryContextItem = React.createClass({displayName: "RoomEntryContextItem",
     mixins: [loop.shared.mixins.WindowCloseMixin],
 
     propTypes: {
       mozLoop: React.PropTypes.object.isRequired,
       roomUrls: React.PropTypes.array
     },
 
@@ -607,27 +603,40 @@ loop.panel = (function(_, mozL10n) {
           ), 
           React.createElement(RoomEntryContextItem, {mozLoop: this.props.mozLoop, 
                                 roomUrls: this.props.room.decryptedContext.urls})
         )
       );
     }
   });
 
+  /*
+   * User profile prop can be either an object or null as per mozLoopAPI
+   * and there is no way to express this with React 0.12.2
+   */
+  function userProfileValidator(props, propName, componentName) {
+    if (Object.prototype.toString.call(props[propName]) !== "[object Object]" &&
+        !_.isNull(props[propName])) {
+      return new Error("Required prop `" + propName +
+        "` was not correctly specified in `" + componentName + "`.");
+    }
+  }
+
   /**
    * Room list.
    */
   var RoomList = React.createClass({displayName: "RoomList",
     mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       mozLoop: React.PropTypes.object.isRequired,
       store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
-      userDisplayName: React.PropTypes.string.isRequired  // for room creation
+      // Used for room creation, associated with room owner.
+      userProfile: userProfileValidator
     },
 
     getInitialState: function() {
       return this.props.store.getStoreState();
     },
 
     componentDidMount: function() {
       this.listenTo(this.props.store, "change", this._onStoreStateChanged);
@@ -658,16 +667,21 @@ loop.panel = (function(_, mozL10n) {
     _getListHeading: function() {
       var numRooms = this.state.rooms.length;
       if (numRooms === 0) {
         return mozL10n.get("rooms_list_no_current_conversations");
       }
       return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
     },
 
+    _getUserDisplayName: function() {
+      return this.props.userProfile && this.props.userProfile.email ||
+        mozL10n.get("display_name_guest");
+    },
+
     render: function() {
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
       return (
         React.createElement("div", {className: "rooms"}, 
@@ -682,17 +696,17 @@ loop.panel = (function(_, mozL10n) {
                   room: room})
               );
             }, this)
           ), 
           React.createElement(NewRoomView, {dispatcher: this.props.dispatcher, 
             mozLoop: this.props.mozLoop, 
             pendingOperation: this.state.pendingCreation ||
               this.state.pendingInitialRetrieval, 
-            userDisplayName: this.props.userDisplayName})
+            userDisplayName: this._getUserDisplayName()})
         )
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
@@ -810,25 +824,23 @@ loop.panel = (function(_, mozL10n) {
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       mozLoop: React.PropTypes.object.isRequired,
       notifications: React.PropTypes.object.isRequired,
       roomStore:
         React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
       selectedTab: React.PropTypes.string,
       // Used only for unit tests.
-      showTabButtons: React.PropTypes.bool,
-      // Mostly used for UI components showcase and unit tests
-      userProfile: React.PropTypes.object
+      showTabButtons: React.PropTypes.bool
     },
 
     getInitialState: function() {
       return {
         hasEncryptionKey: this.props.mozLoop.hasEncryptionKey,
-        userProfile: this.props.userProfile || this.props.mozLoop.userProfile,
+        userProfile: this.props.mozLoop.userProfile,
         gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen")
       };
     },
 
     _serviceErrorToShow: function() {
       if (!this.props.mozLoop.errors ||
           !Object.keys(this.props.mozLoop.errors).length) {
         return null;
@@ -913,21 +925,16 @@ loop.panel = (function(_, mozL10n) {
     },
 
     componentWillUnmount: function() {
       window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
       window.removeEventListener("GettingStartedSeen", this._gettingStartedSeen);
       window.removeEventListener("UIAction", this._UIActionHandler);
     },
 
-    _getUserDisplayName: function() {
-      return this.state.userProfile && this.state.userProfile.email ||
-             mozL10n.get("display_name_guest");
-    },
-
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
 
       if (!this.state.gettingStartedSeen) {
         return (
           React.createElement("div", null, 
             React.createElement(NotificationListView, {
               clearOnDocumentHidden: true, 
@@ -957,17 +964,17 @@ loop.panel = (function(_, mozL10n) {
             buttonsHidden: hideButtons, 
             mozLoop: this.props.mozLoop, 
             ref: "tabView", 
             selectedTab: this.props.selectedTab}, 
             React.createElement(Tab, {name: "rooms"}, 
               React.createElement(RoomList, {dispatcher: this.props.dispatcher, 
                         mozLoop: this.props.mozLoop, 
                         store: this.props.roomStore, 
-                        userDisplayName: this._getUserDisplayName()}), 
+                        userProfile: this.state.userProfile}), 
               React.createElement(ToSView, null)
             ), 
             React.createElement(Tab, {name: "contacts"}, 
               React.createElement(ContactsList, {
                 notifications: this.props.notifications, 
                 selectTab: this.selectTab, 
                 startForm: this.startForm})
             ), 
@@ -987,22 +994,21 @@ loop.panel = (function(_, mozL10n) {
               React.createElement(ContactDetailsForm, {
                 mode: "import", 
                 ref: "contacts_import", 
                 selectTab: this.selectTab})
             )
           ), 
           React.createElement("div", {className: "footer"}, 
             React.createElement("div", {className: "user-details"}, 
-              React.createElement(UserIdentity, {displayName: this._getUserDisplayName()}), 
               React.createElement(AvailabilityDropdown, null)
             ), 
             React.createElement("div", {className: "signin-details"}, 
-              React.createElement(AuthLink, null), 
-              React.createElement("div", {className: "footer-signin-separator"}), 
+              React.createElement(AccountLink, {fxAEnabled: this.props.mozLoop.fxAEnabled, 
+                           userProfile: this.state.userProfile}), 
               React.createElement(SettingsDropdown, {mozLoop: this.props.mozLoop})
             )
           )
         )
       );
     }
   });
 
@@ -1033,24 +1039,23 @@ loop.panel = (function(_, mozL10n) {
 
     // Notify the window that we've finished initalization and initial layout
     var evtObject = document.createEvent("Event");
     evtObject.initEvent("loopPanelInitialized", true, false);
     window.dispatchEvent(evtObject);
   }
 
   return {
-    init: init,
-    AuthLink: AuthLink,
+    AccountLink: AccountLink,
     AvailabilityDropdown: AvailabilityDropdown,
     GettingStartedView: GettingStartedView,
+    init: init,
     NewRoomView: NewRoomView,
     PanelView: PanelView,
     RoomEntry: RoomEntry,
     RoomList: RoomList,
     SettingsDropdown: SettingsDropdown,
     SignInRequestView: SignInRequestView,
-    ToSView: ToSView,
-    UserIdentity: UserIdentity
+    ToSView: ToSView
   };
 })(_, document.mozL10n);
 
 document.addEventListener("DOMContentLoaded", loop.panel.init);
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -138,46 +138,45 @@ loop.panel = (function(_, mozL10n) {
             navigator.mozLoop.doNotDisturb = true;
             break;
         }
         this.hideDropdownMenu();
       }.bind(this);
     },
 
     render: function() {
-      // XXX https://github.com/facebook/react/issues/310 for === htmlFor
       var cx = React.addons.classSet;
-      var availabilityStatus = cx({
-        "status": true,
-        "status-dnd": this.state.doNotDisturb,
-        "status-available": !this.state.doNotDisturb
-      });
       var availabilityDropdown = cx({
         "dropdown-menu": true,
         "hide": !this.state.showMenu
       });
+      var statusIcon = cx({
+        "status-unavailable": this.state.doNotDisturb,
+        "status-available": !this.state.doNotDisturb
+      });
       var availabilityText = this.state.doNotDisturb ?
-                              mozL10n.get("display_name_dnd_status") :
-                              mozL10n.get("display_name_available_status");
+                             mozL10n.get("display_name_dnd_status") :
+                             mozL10n.get("display_name_available_status");
 
       return (
         <div className="dropdown">
-          <p className="dnd-status" onClick={this.toggleDropdownMenu} ref="menu-button">
-            <span>{availabilityText}</span>
-            <i className={availabilityStatus}></i>
+          <p className="dnd-status">
+            <span className={statusIcon}
+                  onClick={this.toggleDropdownMenu}
+                  ref="menu-button">
+              {availabilityText}
+            </span>
           </p>
           <ul className={availabilityDropdown}>
-            <li className="dropdown-menu-item dnd-make-available"
+            <li className="dropdown-menu-item status-available"
                 onClick={this.changeAvailability("available")}>
-              <i className="status status-available"></i>
               <span>{mozL10n.get("display_name_available_status")}</span>
             </li>
-            <li className="dropdown-menu-item dnd-make-unavailable"
+            <li className="dropdown-menu-item status-unavailable"
                 onClick={this.changeAvailability("do-not-disturb")}>
-              <i className="status status-dnd"></i>
               <span>{mozL10n.get("display_name_dnd_status")}</span>
             </li>
           </ul>
         </div>
       );
     }
   });
 
@@ -404,17 +403,17 @@ loop.panel = (function(_, mozL10n) {
       this.closeWindow();
     },
 
     render: function() {
       var cx = React.addons.classSet;
 
       return (
         <div className="settings-menu dropdown">
-          <a className="button-settings"
+          <button className="button-settings"
              onClick={this.toggleDropdownMenu}
              ref="menu-button"
              title={mozL10n.get("settings_menu_button_tooltip")} />
           <ul className={cx({"dropdown-menu": true, hide: !this.state.showMenu})}>
             <SettingsDropdownEntry displayed={false}
                                    icon="settings"
                                    label={mozL10n.get("settings_menu_item_settings")}
                                    onClick={this.handleClickSettingsEntry} />
@@ -438,55 +437,52 @@ loop.panel = (function(_, mozL10n) {
         </div>
       );
     }
   });
 
   /**
    * FxA sign in/up link component.
    */
-  var AuthLink = React.createClass({
+  var AccountLink = React.createClass({
     mixins: [sharedMixins.WindowCloseMixin],
 
-    handleSignUpLinkClick: function() {
+    propTypes: {
+      fxAEnabled: React.PropTypes.bool.isRequired,
+      userProfile: userProfileValidator
+    },
+
+    handleSignInLinkClick: function() {
       navigator.mozLoop.logInToFxA();
       this.closeWindow();
     },
 
     render: function() {
-      if (!navigator.mozLoop.fxAEnabled || navigator.mozLoop.userProfile) {
+      if (!this.props.fxAEnabled) {
         return null;
       }
+
+      if (this.props.userProfile && this.props.userProfile.email) {
+        return (
+          <div className="user-identity">
+            {loop.shared.utils.truncate(this.props.userProfile.email, 24)}
+          </div>
+        );
+      }
+
       return (
         <p className="signin-link">
-          <a href="#" onClick={this.handleSignUpLinkClick}>
+          <a href="#" onClick={this.handleSignInLinkClick}>
             {mozL10n.get("panel_footer_signin_or_signup_link")}
           </a>
         </p>
       );
     }
   });
 
-  /**
-   * FxA user identity (guest/authenticated) component.
-   */
-  var UserIdentity = React.createClass({
-    propTypes: {
-      displayName: React.PropTypes.string.isRequired
-    },
-
-    render: function() {
-      return (
-        <p className="user-identity">
-          {this.props.displayName}
-        </p>
-      );
-    }
-  });
-
   var RoomEntryContextItem = React.createClass({
     mixins: [loop.shared.mixins.WindowCloseMixin],
 
     propTypes: {
       mozLoop: React.PropTypes.object.isRequired,
       roomUrls: React.PropTypes.array
     },
 
@@ -607,27 +603,40 @@ loop.panel = (function(_, mozL10n) {
           </h2>
           <RoomEntryContextItem mozLoop={this.props.mozLoop}
                                 roomUrls={this.props.room.decryptedContext.urls} />
         </div>
       );
     }
   });
 
+  /*
+   * User profile prop can be either an object or null as per mozLoopAPI
+   * and there is no way to express this with React 0.12.2
+   */
+  function userProfileValidator(props, propName, componentName) {
+    if (Object.prototype.toString.call(props[propName]) !== "[object Object]" &&
+        !_.isNull(props[propName])) {
+      return new Error("Required prop `" + propName +
+        "` was not correctly specified in `" + componentName + "`.");
+    }
+  }
+
   /**
    * Room list.
    */
   var RoomList = React.createClass({
     mixins: [Backbone.Events, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       mozLoop: React.PropTypes.object.isRequired,
       store: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
-      userDisplayName: React.PropTypes.string.isRequired  // for room creation
+      // Used for room creation, associated with room owner.
+      userProfile: userProfileValidator
     },
 
     getInitialState: function() {
       return this.props.store.getStoreState();
     },
 
     componentDidMount: function() {
       this.listenTo(this.props.store, "change", this._onStoreStateChanged);
@@ -658,16 +667,21 @@ loop.panel = (function(_, mozL10n) {
     _getListHeading: function() {
       var numRooms = this.state.rooms.length;
       if (numRooms === 0) {
         return mozL10n.get("rooms_list_no_current_conversations");
       }
       return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
     },
 
+    _getUserDisplayName: function() {
+      return this.props.userProfile && this.props.userProfile.email ||
+        mozL10n.get("display_name_guest");
+    },
+
     render: function() {
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
       return (
         <div className="rooms">
@@ -682,17 +696,17 @@ loop.panel = (function(_, mozL10n) {
                   room={room} />
               );
             }, this)
           }</div>
           <NewRoomView dispatcher={this.props.dispatcher}
             mozLoop={this.props.mozLoop}
             pendingOperation={this.state.pendingCreation ||
               this.state.pendingInitialRetrieval}
-            userDisplayName={this.props.userDisplayName} />
+            userDisplayName={this._getUserDisplayName()} />
         </div>
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
@@ -810,25 +824,23 @@ loop.panel = (function(_, mozL10n) {
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       mozLoop: React.PropTypes.object.isRequired,
       notifications: React.PropTypes.object.isRequired,
       roomStore:
         React.PropTypes.instanceOf(loop.store.RoomStore).isRequired,
       selectedTab: React.PropTypes.string,
       // Used only for unit tests.
-      showTabButtons: React.PropTypes.bool,
-      // Mostly used for UI components showcase and unit tests
-      userProfile: React.PropTypes.object
+      showTabButtons: React.PropTypes.bool
     },
 
     getInitialState: function() {
       return {
         hasEncryptionKey: this.props.mozLoop.hasEncryptionKey,
-        userProfile: this.props.userProfile || this.props.mozLoop.userProfile,
+        userProfile: this.props.mozLoop.userProfile,
         gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen")
       };
     },
 
     _serviceErrorToShow: function() {
       if (!this.props.mozLoop.errors ||
           !Object.keys(this.props.mozLoop.errors).length) {
         return null;
@@ -913,21 +925,16 @@ loop.panel = (function(_, mozL10n) {
     },
 
     componentWillUnmount: function() {
       window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
       window.removeEventListener("GettingStartedSeen", this._gettingStartedSeen);
       window.removeEventListener("UIAction", this._UIActionHandler);
     },
 
-    _getUserDisplayName: function() {
-      return this.state.userProfile && this.state.userProfile.email ||
-             mozL10n.get("display_name_guest");
-    },
-
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
 
       if (!this.state.gettingStartedSeen) {
         return (
           <div>
             <NotificationListView
               clearOnDocumentHidden={true}
@@ -957,17 +964,17 @@ loop.panel = (function(_, mozL10n) {
             buttonsHidden={hideButtons}
             mozLoop={this.props.mozLoop}
             ref="tabView"
             selectedTab={this.props.selectedTab}>
             <Tab name="rooms">
               <RoomList dispatcher={this.props.dispatcher}
                         mozLoop={this.props.mozLoop}
                         store={this.props.roomStore}
-                        userDisplayName={this._getUserDisplayName()} />
+                        userProfile={this.state.userProfile} />
               <ToSView />
             </Tab>
             <Tab name="contacts">
               <ContactsList
                 notifications={this.props.notifications}
                 selectTab={this.selectTab}
                 startForm={this.startForm} />
             </Tab>
@@ -987,22 +994,21 @@ loop.panel = (function(_, mozL10n) {
               <ContactDetailsForm
                 mode="import"
                 ref="contacts_import"
                 selectTab={this.selectTab}/>
             </Tab>
           </TabView>
           <div className="footer">
             <div className="user-details">
-              <UserIdentity displayName={this._getUserDisplayName()} />
               <AvailabilityDropdown />
             </div>
             <div className="signin-details">
-              <AuthLink />
-              <div className="footer-signin-separator" />
+              <AccountLink fxAEnabled={this.props.mozLoop.fxAEnabled}
+                           userProfile={this.state.userProfile}/>
               <SettingsDropdown mozLoop={this.props.mozLoop}/>
             </div>
           </div>
         </div>
       );
     }
   });
 
@@ -1033,24 +1039,23 @@ loop.panel = (function(_, mozL10n) {
 
     // Notify the window that we've finished initalization and initial layout
     var evtObject = document.createEvent("Event");
     evtObject.initEvent("loopPanelInitialized", true, false);
     window.dispatchEvent(evtObject);
   }
 
   return {
-    init: init,
-    AuthLink: AuthLink,
+    AccountLink: AccountLink,
     AvailabilityDropdown: AvailabilityDropdown,
     GettingStartedView: GettingStartedView,
+    init: init,
     NewRoomView: NewRoomView,
     PanelView: PanelView,
     RoomEntry: RoomEntry,
     RoomList: RoomList,
     SettingsDropdown: SettingsDropdown,
     SignInRequestView: SignInRequestView,
-    ToSView: ToSView,
-    UserIdentity: UserIdentity
+    ToSView: ToSView
   };
 })(_, document.mozL10n);
 
 document.addEventListener("DOMContentLoaded", loop.panel.init);
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -455,33 +455,16 @@ loop.roomViews = (function(mozL10n) {
      * @return {Object} The first context URL found on the `roomData` object.
      */
     _getURL: function(roomData) {
       roomData = roomData || this.props.roomData;
       return this.props.roomData.roomContextUrls &&
         this.props.roomData.roomContextUrls[0];
     },
 
-    /**
-     * Truncate a string if it exceeds the length as defined in `maxLen`, which
-     * is defined as '72' characters by default. If the string needs trimming,
-     * it'll be suffixed with the unicode ellipsis char, \u2026.
-     *
-     * @param  {String} str    The string to truncate, if needed.
-     * @param  {Number} maxLen Maximum number of characters that the string is
-     *                         allowed to contain. Optional, defaults to 72.
-     * @return {String} Truncated version of `str`.
-     */
-    _truncate: function(str, maxLen) {
-      if (!maxLen) {
-        maxLen = 72;
-      }
-      return (str.length > maxLen) ? str.substr(0, maxLen) + "…" : str;
-    },
-
     render: function() {
       if (!this.state.show) {
         return null;
       }
 
       var url = this._getURL();
       var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
       var urlDescription = url && url.description || "";
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -455,33 +455,16 @@ loop.roomViews = (function(mozL10n) {
      * @return {Object} The first context URL found on the `roomData` object.
      */
     _getURL: function(roomData) {
       roomData = roomData || this.props.roomData;
       return this.props.roomData.roomContextUrls &&
         this.props.roomData.roomContextUrls[0];
     },
 
-    /**
-     * Truncate a string if it exceeds the length as defined in `maxLen`, which
-     * is defined as '72' characters by default. If the string needs trimming,
-     * it'll be suffixed with the unicode ellipsis char, \u2026.
-     *
-     * @param  {String} str    The string to truncate, if needed.
-     * @param  {Number} maxLen Maximum number of characters that the string is
-     *                         allowed to contain. Optional, defaults to 72.
-     * @return {String} Truncated version of `str`.
-     */
-    _truncate: function(str, maxLen) {
-      if (!maxLen) {
-        maxLen = 72;
-      }
-      return (str.length > maxLen) ? str.substr(0, maxLen) + "…" : str;
-    },
-
     render: function() {
       if (!this.state.show) {
         return null;
       }
 
       var url = this._getURL();
       var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
       var urlDescription = url && url.description || "";
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -417,39 +417,36 @@ p {
 
 .dropdown-menu {
   position: absolute;
   bottom: 0;
   left: 0;
   background-color: #fdfdfd;
   box-shadow: 0 1px 3px rgba(0,0,0,.3);
   list-style: none;
-  padding: 5px;
   border-radius: 2px;
 }
 
 html[dir="rtl"] .dropdown-menu {
   left: auto;
   right: 0;
 }
 
 .dropdown-menu-item {
+  width: 100%;
   text-align: start;
-  margin: .3em 0;
-  padding: .2em .5em;
+  padding: .5em 15px;
   cursor: pointer;
   border: 1px solid transparent;
-  border-radius: 2px;
   font-size: 1em;
   white-space: nowrap;
 }
 
 .dropdown-menu-item:hover {
-  border: 1px solid #ccc;
-  background-color: #eee;
+  background-color: #dbf7ff;
 }
 
 .dropdown-menu-item > .icon {
   background-repeat: no-repeat;
   display: inline-block;
 }
 
 .dropdown-menu-separator {
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -1067,16 +1067,17 @@ html[dir="rtl"] .room-context-btn-close 
 
 .media-layout > .media-wrapper {
   display: flex;
   flex-flow: column wrap;
   height: 100%;
 }
 
 .media-wrapper > .focus-stream {
+  display: flex;
   /* We want this to be the width, minus 200px which is for the right-side text
      chat and video displays. */
   width: calc(100% - 200px);
   /* 100% height to fill up media-layout, thus forcing other elements into the
      second column that's 200px wide */
   height: 100%;
   background-color: #4E4E4E;
 }
@@ -1342,31 +1343,20 @@ html[dir="rtl"] .room-context-btn-close 
 .standalone .room-conversation-wrapper .video-layout-wrapper {
   /* 50px = height of header, 3em = height of footer */
   /* XXX This is currently wrong for narrow/mobile display,
      bug 1168829  will hopefully address it. */
   height: calc(100% - 50px - 3em);
 }
 
 .standalone .room-conversation-wrapper .room-inner-info-area {
-  position: absolute;
-  /* `top` is chosen to vertically position the area near the center
-     of the media element. */
-  top: calc(50% - 140px);
-  left: 25%;
-  z-index: 1000;
-  /* `width` here is specified by the design spec. */
-  width: 250px;
   color: #fff;
-  box-sizing: content-box;
-}
-
-html[dir="rtl"] .standalone .room-conversation-wrapper .room-inner-info-area {
-  right: 25%;
-  left: auto;
+  margin: auto;
+  /* 290px is the width of the widest info child, i.e., a tile */
+  width: 290px;
 }
 
 .standalone .prompt-media-message {
   padding-top: 136px; /* Fallback for browsers that don't support calc() */
   /* 122px is 2x the intrinsic height of the background-image, and
      1rem puts one line of margin between the background-image and
      supporting text. */
   padding-top: calc(122px + 1rem);
--- a/browser/components/loop/content/shared/img/icons-10x10.svg
+++ b/browser/components/loop/content/shared/img/icons-10x10.svg
@@ -24,16 +24,17 @@
     }
   </style>
   <defs>
     <polygon id="close-shape" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668 3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
     <path id="dropdown-shape" fill-rule="evenodd" d="M9,3L4.984,7L1,3H9z"/>
     <polygon id="expand-shape" points="10,0 4.838,0 6.506,1.669 0,8.175 1.825,10 8.331,3.494 10,5.162"/>
     <path id="edit-shape" d="M5.493,1.762l2.745,2.745L2.745,10H0V7.255L5.493,1.762z M2.397,9.155l0.601-0.601L1.446,7.002L0.845,7.603 V8.31H1.69v0.845H2.397z M5.849,3.028c0-0.096-0.048-0.144-0.146-0.144c-0.044,0-0.081,0.015-0.112,0.046L2.014,6.508 C1.983,6.538,1.968,6.577,1.968,6.619c0,0.098,0.048,0.146,0.144,0.146c0.044,0,0.081-0.015,0.112-0.046l3.579-3.577 C5.834,3.111,5.849,3.073,5.849,3.028z M10,2.395c0,0.233-0.081,0.431-0.245,0.595L8.66,4.085L5.915,1.34L7.01,0.25 C7.168,0.083,7.366,0,7.605,0c0.233,0,0.433,0.083,0.601,0.25l1.55,1.544C9.919,1.966,10,2.166,10,2.395z"/>
     <rect id="minimize-shape" y="3.6" width="10" height="2.8"/>
+    <path id="cog-shape" d="M122.285722,122.071424 C122.285722,122.936938 121.579806,123.642854 120.714291,123.642854 C119.848777,123.642854 119.142861,122.936938 119.142861,122.071424 C119.142861,121.205909 119.848777,120.499993 120.714291,120.499993 C121.579806,120.499993 122.285722,121.205909 122.285722,122.071424 L122.285722,122.071424 Z M125.428583,121.402338 C125.428583,121.297985 125.354922,121.199771 125.250569,121.181356 L124.127242,121.009481 C124.065858,120.806913 123.97992,120.604346 123.875567,120.407917 C124.084273,120.119413 124.311394,119.849323 124.520099,119.566957 C124.550791,119.523988 124.569207,119.481019 124.569207,119.425773 C124.569207,119.376666 124.55693,119.327559 124.526238,119.290729 C124.268425,118.928563 123.838737,118.547982 123.513402,118.247201 C123.470433,118.21037 123.415187,118.185817 123.359942,118.185817 C123.304696,118.185817 123.249451,118.204232 123.21262,118.241062 L122.340967,118.897871 C122.162954,118.805795 121.978802,118.732134 121.788511,118.67075 L121.616636,117.541285 C121.604359,117.436932 121.506145,117.357133 121.395654,117.357133 L120.032929,117.357133 C119.922438,117.357133 119.8365,117.430793 119.811947,117.529008 C119.713732,117.897312 119.676902,118.296308 119.633933,118.67075 C119.443642,118.732134 119.253352,118.811933 119.075338,118.904009 L118.228239,118.247201 C118.179132,118.21037 118.123886,118.185817 118.068641,118.185817 C117.859935,118.185817 117.031251,119.082023 116.88393,119.28459 C116.853238,119.327559 116.828684,119.370528 116.828684,119.425773 C116.828684,119.481019 116.853238,119.530126 116.890068,119.573095 C117.117189,119.849323 117.338171,120.125551 117.546877,120.420194 C117.448662,120.604346 117.368863,120.788498 117.307479,120.984927 L116.165737,121.156802 C116.073661,121.175217 116,121.285709 116,121.377785 L116,122.74051 C116,122.844862 116.073661,122.943077 116.178014,122.961492 L117.301341,123.127229 C117.362725,123.335934 117.448662,123.538502 117.553015,123.73493 C117.34431,124.023435 117.117189,124.293525 116.908483,124.575891 C116.877791,124.61886 116.859376,124.661829 116.859376,124.717074 C116.859376,124.766182 116.871653,124.815289 116.902345,124.858258 C117.160158,125.214285 117.589846,125.594866 117.915181,125.889509 C117.95815,125.932478 118.013395,125.957031 118.068641,125.957031 C118.123886,125.957031 118.179132,125.938616 118.222101,125.901786 L119.087615,125.244977 C119.265629,125.337053 119.449781,125.410714 119.640071,125.472098 L119.811947,126.601563 C119.824223,126.705916 119.922438,126.785715 120.032929,126.785715 L121.395654,126.785715 C121.506145,126.785715 121.592083,126.712054 121.616636,126.61384 C121.714851,126.245536 121.751681,125.84654 121.79465,125.472098 C121.98494,125.410714 122.175231,125.330914 122.353244,125.238838 L123.200343,125.901786 C123.249451,125.932478 123.304696,125.957031 123.359942,125.957031 C123.568647,125.957031 124.397331,125.054686 124.544653,124.858258 C124.581483,124.815289 124.599899,124.77232 124.599899,124.717074 C124.599899,124.661829 124.575345,124.606583 124.538515,124.563614 C124.311394,124.287386 124.090411,124.017297 123.881706,123.716515 C123.97992,123.538502 124.053581,123.35435 124.121103,123.157921 L125.256707,122.986046 C125.354922,122.96763 125.428583,122.857139 125.428583,122.765063 L125.428583,121.402338 Z" transform="translate(-116 -117)" fill="#666" fill-rule="evenodd"/>
   </defs>
   <use id="close" xlink:href="#close-shape"/>
   <use id="close-active" xlink:href="#close-shape"/>
   <use id="close-disabled" xlink:href="#close-shape"/>
   <use id="close-darkergrey" xlink:href="#close-shape"/>
   <use id="dropdown" xlink:href="#dropdown-shape"/>
   <use id="dropdown-white" xlink:href="#dropdown-shape"/>
   <use id="dropdown-active" xlink:href="#dropdown-shape"/>
@@ -43,9 +44,10 @@
   <use id="edit-disabled" xlink:href="#edit-shape"/>
   <use id="edit-white" xlink:href="#edit-shape"/>
   <use id="expand" xlink:href="#expand-shape"/>
   <use id="expand-active" xlink:href="#expand-shape"/>
   <use id="expand-disabled" xlink:href="#expand-shape"/>
   <use id="minimize" xlink:href="#minimize-shape"/>
   <use id="minimize-active" xlink:href="#minimize-shape"/>
   <use id="minimize-disabled" xlink:href="#minimize-shape"/>
+  <use id="settings-cog" xlink:href="#cog-shape"/>
 </svg>
--- a/browser/components/loop/content/shared/img/icons-16x16.svg
+++ b/browser/components/loop/content/shared/img/icons-16x16.svg
@@ -82,16 +82,18 @@
       <path fill="#ccc" d="M14.191,4.579c0-1.149-0.832-2.08-1.857-2.08h-0.902V1.997C11.432,0.894,10.633,0,9.648,0H6.352 C5.367,0,4.568,0.894,4.568,1.997v0.502H3.666c-1.026,0-1.857,0.932-1.857,2.08v1.389h1.064l0.785,8.416 C3.742,15.29,4.499,16,5.383,16h5.231c0.883,0,1.64-0.709,1.724-1.614l0.788-8.417h1.064V4.579z M5.535,2.218V2.065 c0-0.58,0.42-1.05,0.938-1.05h3.055c0.518,0,0.937,0.47,0.937,1.05v0.153c0,0.098-0.016,0.191-0.038,0.281H5.573 C5.55,2.409,5.535,2.316,5.535,2.218z M11.184,14.277c-0.029,0.305-0.29,0.562-0.57,0.562H5.383c-0.281,0-0.541-0.257-0.57-0.562 L4.038,5.969h7.924L11.184,14.277z"/>
       <rect x="7.612" y="7.291" fill="#ccc" width="0.774" height="6.191"/>
       <polyline fill="#ccc" points="9.934,7.291 9.16,7.291 9.16,13.482 9.934,13.482  "/>
       <rect x="6.065" y="7.291" fill="#ccc" width="0.774" height="6.191"/>
     </g>
     <g id="globe-shape" transform="translate(-40.000000, -40.000000)" fill-rule="evenodd">
       <path d="M48,56 C52.418278,56 56,52.418278 56,48 C56,43.581722 52.418278,40 48,40 C43.581722,40 40,43.581722 40,48 C40,52.418278 43.581722,56 48,56 Z M42.630492,47.0453156 C42.5321586,46.9747601 42.3116031,46.9575379 42.3388253,46.7925379 C42.3560475,46.6919823 42.4593808,46.613649 42.5438253,46.5642045 C42.6538253,46.5003156 42.7766031,46.4980934 42.9010475,46.5092045 C42.9310475,46.5119823 42.9866031,46.5069823 43.0021586,46.5297601 C43.010492,46.5425379 43.0277142,46.5492045 43.0416031,46.553649 C43.0743808,46.563649 43.1088253,46.5642045 43.1427142,46.5703156 C43.1932697,46.5803156 43.2288253,46.6247601 43.280492,46.588649 C43.3388253,46.5469823 43.3482697,46.5392045 43.4188253,46.5475379 C43.4827142,46.5553156 43.520492,46.5064268 43.5743808,46.5108712 C43.5910475,46.5125379 43.605492,46.5158712 43.6182697,46.5208712 C43.6238253,46.5030934 43.6316031,46.4869823 43.6427142,46.483649 C43.6693808,46.4753156 43.7282697,46.5403156 43.755492,46.5458712 C43.825492,46.5597601 43.8210475,46.5114268 43.8266031,46.4608712 C43.8616031,46.4542045 43.8782697,46.5030934 43.910492,46.4725379 C43.9093808,46.4825379 43.915492,46.4964268 43.9149364,46.5069823 C43.9221586,46.5114268 43.9288253,46.5114268 43.9360475,46.5064268 C43.9388253,46.5003156 43.9393808,46.4947601 43.9377142,46.488649 C43.9543808,46.4942045 43.9632697,46.4819823 43.965492,46.4614268 C43.9777142,46.4630934 43.9982697,46.4553156 44.0110475,46.4575379 C44.0199364,46.4208712 44.0416031,46.3730934 44.0149364,46.3397601 C44.0221586,46.3380934 44.0288253,46.3369823 44.0360475,46.3347601 C44.0366031,46.2997601 44.060492,46.2830934 44.0616031,46.253649 C44.0232697,46.248649 43.9816031,46.2508712 43.9421586,46.2519823 C43.965492,46.2303156 44.0216031,46.1830934 44.0271586,46.1542045 C44.0371586,46.103649 43.9699364,46.0719823 43.975492,46.0114268 C43.9816031,46.043649 44.0182697,46.1169823 44.0521586,46.1280934 C44.1282697,46.153649 44.1066031,46.0775379 44.1121586,46.0380934 C44.130492,45.9130934 44.2432697,46.0164268 44.2449364,46.0819823 C44.2782697,46.0064268 44.3599364,46.0897601 44.3232697,46.1542045 C44.3049364,46.1858712 44.2799364,46.173649 44.3021586,46.2147601 C44.3182697,46.243649 44.3416031,46.2447601 44.3749364,46.2369823 C44.3843808,46.2197601 44.3921586,46.2003156 44.3916031,46.1803156 C44.4499364,46.1619823 44.4877142,46.2314268 44.4538253,46.273649 C44.4971586,46.2497601 44.5421586,46.2269823 44.5888253,46.2153156 C44.560492,46.1197601 44.5327142,46.0253156 44.5510475,45.9219823 C44.5549364,45.8997601 44.5582697,45.873649 44.5749364,45.8564268 C44.5966031,45.8342045 44.5693808,45.8430934 44.5671586,45.8280934 C44.5616031,45.7825379 44.6093808,45.7369823 44.6271586,45.6969823 C44.5766031,45.6853156 44.6232697,45.5869823 44.6560475,45.5703156 C44.6916031,45.5519823 44.7982697,45.5814268 44.8049364,45.5530934 C44.8260475,45.5653156 44.8460475,45.5819823 44.8721586,45.5825379 C44.9299364,45.5830934 44.9693808,45.5842045 45.0093808,45.6314268 C45.030492,45.6553156 45.0593808,45.7103156 45.0960475,45.7142045 C45.0949364,45.7547601 45.1416031,45.7853156 45.0927142,45.8197601 C45.0543808,45.8464268 45.0010475,45.8397601 44.9849364,45.8914268 C44.9749364,45.923649 44.9443808,45.9380934 44.990492,45.9608712 C45.0116031,45.9708712 45.0360475,45.973649 45.0588253,45.9730934 C45.0638253,46.0053156 45.0793808,46.0458712 45.1199364,46.0408712 C45.1988253,46.0314268 45.2149364,45.9330934 45.2782697,45.8997601 C45.3793808,45.8464268 45.3632697,46.0780934 45.4521586,46.0192045 C45.4732697,46.0058712 45.4732697,45.9469823 45.4832697,45.9247601 C45.5043808,45.8769823 45.5299364,45.8297601 45.560492,45.7869823 C45.5988253,45.7342045 45.6477142,45.678649 45.6310475,45.608649 C45.6221586,45.5703156 45.5477142,45.5542045 45.5138253,45.5269823 C45.4727142,45.4942045 45.4343808,45.458649 45.4093808,45.4125379 C45.3949364,45.3853156 45.3860475,45.3747601 45.4099364,45.3619823 C45.4238253,45.3542045 45.4188253,45.338649 45.4138253,45.3280934 C45.3888253,45.2742045 45.3193808,45.1792045 45.4160475,45.1464268 C45.4371586,45.1392045 45.4821586,45.0703156 45.4849364,45.0430934 C45.4899364,44.9980934 45.4221586,44.9580934 45.4527142,44.9130934 C45.475492,44.8792045 45.5310475,44.858649 45.560492,44.8247601 C45.5738253,44.8092045 45.5899364,44.7953156 45.6110475,44.7908712 C45.6127142,44.7725379 45.6166031,44.7525379 45.6321586,44.7403156 C45.6582697,44.7214268 45.6982697,44.7303156 45.7277142,44.7214268 C45.7760475,44.7064268 45.7977142,44.6558712 45.8360475,44.6275379 C45.8671586,44.6042045 45.9038253,44.6125379 45.9371586,44.5964268 C45.955492,44.5875379 45.9632697,44.5680934 45.9810475,44.5597601 C46.025492,44.5380934 46.0760475,44.5725379 46.0960475,44.6103156 C46.1443808,44.7003156 46.2038253,44.838649 46.3399364,44.8025379 C46.3943808,44.7875379 46.435492,44.7425379 46.4527142,44.6908712 C46.4693808,44.6419823 46.4499364,44.6014268 46.4527142,44.5525379 C46.4571586,44.4680934 46.5416031,44.413649 46.5499364,44.328649 C46.4866031,44.3297601 46.5193808,44.2897601 46.4988253,44.2542045 C46.475492,44.2130934 46.4221586,44.2430934 46.3866031,44.2364268 C46.4227142,44.1503156 46.4227142,44.1192045 46.3377142,44.0780934 C46.3010475,44.0597601 46.2393808,43.973649 46.2116031,43.9769823 C46.2349364,43.9447601 46.2899364,43.998649 46.3066031,44.0142045 C46.3432697,44.0492045 46.3760475,44.0653156 46.4282697,44.0692045 C46.4138253,44.0464268 46.4060475,44.0058712 46.4160475,43.9803156 C46.425492,43.9564268 46.4016031,43.9319823 46.4038253,43.9019823 C46.4632697,43.9780934 46.4532697,44.0653156 46.4877142,44.148649 C46.5021586,44.1842045 46.5360475,44.2075379 46.5510475,44.243649 C46.570492,44.2892045 46.5582697,44.2892045 46.5988253,44.3169823 C46.6227142,44.3330934 46.6310475,44.363649 46.635492,44.3903156 C46.6427142,44.4375379 46.6599364,44.4169823 46.6849364,44.443649 C46.6999364,44.4597601 46.7382697,44.4625379 46.730492,44.4953156 C46.725492,44.518649 46.7099364,44.538649 46.7066031,44.5619823 C46.6966031,44.633649 46.8360475,44.5375379 46.8493808,44.5275379 C46.8788253,44.5053156 46.9277142,44.5003156 46.9516031,44.473649 C46.975492,44.4458712 46.9699364,44.4058712 46.9960475,44.3792045 C47.0277142,44.3469823 47.0582697,44.3692045 47.0971586,44.3625379 C47.1410475,44.3558712 47.1793808,44.3208712 47.2121586,44.293649 C47.2821586,44.2353156 47.3260475,44.1708712 47.3849364,44.103649 C47.3599364,44.1097601 47.2721586,44.1730934 47.2677142,44.1147601 C47.2338253,44.113649 47.1582697,44.108649 47.1460475,44.0719823 C47.1371586,44.0453156 47.140492,44.0164268 47.1399364,43.988649 C47.1393808,43.9603156 47.1043808,43.9697601 47.0810475,43.9547601 C47.0343808,43.9242045 47.0110475,43.8697601 46.9610475,43.8430934 C46.8816031,43.8014268 46.8332697,43.7347601 46.7860475,43.6630934 C46.7582697,43.6208712 46.6610475,43.5358712 46.6677142,43.4853156 C46.6727142,43.4525379 46.6999364,43.4169823 46.6982697,43.383649 C46.6966031,43.3547601 46.6732697,43.338649 46.6760475,43.3053156 C46.6799364,43.2664268 46.5860475,43.1992045 46.6677142,43.1914268 C46.6943808,43.1892045 46.6977142,43.158649 46.7260475,43.1414268 C46.7582697,43.1219823 46.7510475,43.1053156 46.7860475,43.1147601 C46.8421586,43.1308712 46.8816031,43.0697601 46.9199364,43.0364268 C46.985492,42.978649 46.880492,42.9769823 46.8749364,42.9342045 C46.8693808,42.8897601 46.8438253,42.8575379 46.8371586,42.8064268 C46.8327142,42.7692045 46.7977142,42.783649 46.7777142,42.7930934 C46.7499364,42.8058712 46.7227142,42.7869823 46.6960475,42.7814268 C46.6710475,42.7764268 46.6510475,42.7358712 46.6221586,42.748649 C46.5999364,42.758649 46.600492,42.7864268 46.5688253,42.7830934 C46.5449364,42.7803156 46.530492,42.7575379 46.5066031,42.7542045 C46.4710475,42.7492045 46.5027142,42.7830934 46.4627142,42.7869823 C46.4338253,42.788649 46.3421586,42.7497601 46.3399364,42.7869823 C46.310492,42.7375379 46.2993808,42.8208712 46.2682697,42.8297601 C46.2349364,42.8397601 46.1982697,42.8303156 46.1649364,42.843649 C46.0910475,42.873649 46.1143808,42.9475379 46.1860475,42.958649 C46.2432697,42.9680934 46.1688253,43.0069823 46.1877142,43.0458712 C46.2043808,43.0803156 46.2099364,43.103649 46.2471586,43.1175379 C46.3093808,43.1403156 46.3749364,43.158649 46.3532697,43.2380934 C46.325492,43.3364268 46.2566031,43.4292045 46.1599364,43.4742045 C46.0677142,43.5169823 46.0427142,43.3997601 45.9727142,43.3703156 C45.9293808,43.3525379 45.8799364,43.358649 45.8349364,43.3642045 C45.8266031,43.3758712 45.8988253,43.3975379 45.9099364,43.418649 C45.9316031,43.4603156 45.8732697,43.4553156 45.8688253,43.4864268 C45.8643808,43.513649 45.8293808,43.5330934 45.8482697,43.5603156 C45.8282697,43.5347601 45.7888253,43.568649 45.775492,43.5830934 C45.755492,43.6053156 45.7593808,43.618649 45.7677142,43.6453156 C45.7849364,43.7003156 45.7027142,43.7569823 45.6532697,43.7497601 C45.6121586,43.7442045 45.5727142,43.7469823 45.5338253,43.7280934 C45.4866031,43.7053156 45.5032697,43.7192045 45.4932697,43.6664268 C45.4838253,43.6175379 45.4160475,43.5964268 45.4560475,43.5353156 C45.4843808,43.4897601 45.4710475,43.4947601 45.465492,43.4514268 C45.4593808,43.4064268 45.4799364,43.4008712 45.515492,43.393649 C45.5543808,43.3858712 45.5716031,43.3175379 45.595492,43.283649 C45.6010475,43.2758712 45.6243808,43.2142045 45.5871586,43.2303156 C45.5649364,43.2392045 45.5816031,43.2658712 45.5488253,43.2714268 C45.5243808,43.2747601 45.5016031,43.2592045 45.4777142,43.2592045 C45.4499364,43.2592045 45.420492,43.2730934 45.395492,43.2553156 C45.4077142,43.2403156 45.4999364,43.1664268 45.4271586,43.1503156 C45.3982697,43.1442045 45.4227142,43.1914268 45.3843808,43.1853156 C45.3760475,43.2230934 45.3310475,43.218649 45.3088253,43.2430934 C45.3177142,43.2030934 45.4027142,43.173649 45.3749364,43.1414268 C45.4349364,43.0880934 45.4471586,43.0792045 45.365492,43.0503156 C45.2371586,43.0053156 45.2471586,42.8742045 45.335492,42.7942045 C45.4160475,42.7214268 45.5488253,42.6292045 45.6266031,42.7503156 C45.710492,42.8797601 45.7638253,42.7858712 45.8327142,42.7025379 C45.8093808,42.6925379 45.830492,42.6864268 45.8227142,42.6580934 C45.7466031,42.688649 45.6788253,42.5903156 45.7310475,42.5308712 C45.7621586,42.4958712 45.8116031,42.5058712 45.8532697,42.4953156 C45.8916031,42.4853156 45.9249364,42.4480934 45.940492,42.4147601 C45.9088253,42.4230934 45.9127142,42.403649 45.9249364,42.3892045 C45.905492,42.3875379 45.8849364,42.3792045 45.8688253,42.373649 C45.8232697,42.3580934 45.8260475,42.3225379 45.7782697,42.3158712 C45.6621586,42.298649 45.8982697,42.1664268 45.7827142,42.1658712 C45.7460475,42.1653156 45.7121586,42.108649 45.6849364,42.1197601 C45.665492,42.1275379 45.6616031,42.1425379 45.6377142,42.1325379 C45.6216031,42.1258712 45.6021586,42.1119823 45.5838253,42.1230934 C45.5416031,42.1492045 45.5338253,42.1175379 45.4932697,42.1303156 C45.4610475,42.1403156 45.4421586,42.1719823 45.4043808,42.163649 C45.4421586,42.1125379 45.4888253,42.0703156 45.5227142,42.0158712 C45.545492,41.9797601 45.5721586,41.9469823 45.6099364,41.9247601 C45.6299364,41.9119823 45.6871586,41.9008712 45.690492,41.8725379 C45.695492,41.8269823 45.6666031,41.8303156 45.6349364,41.8492045 C45.5527142,41.898649 45.4671586,41.9514268 45.3860475,42.003649 C45.3377142,42.0347601 45.2988253,42.0614268 45.2382697,42.0525379 C45.1921586,42.0453156 45.1732697,42.0969823 45.1360475,42.0919823 C45.1182697,42.0208712 44.7199364,42.2808712 44.6716031,42.3025379 C44.5943808,42.3375379 44.5066031,42.3964268 44.4243808,42.4164268 C44.3899364,42.4247601 44.3193808,42.5047601 44.3232697,42.4164268 C44.2816031,42.4114268 44.2499364,42.453649 44.2210475,42.4758712 C44.1810475,42.5069823 44.1327142,42.5253156 44.0899364,42.5525379 C43.9960475,42.6125379 43.9082697,42.683649 43.8210475,42.7514268 C43.7382697,42.8158712 43.655492,42.8908712 43.5688253,42.948649 C43.5388253,42.968649 43.4293808,43.0247601 43.4332697,43.0664268 C43.5110475,43.0808712 43.7743808,42.7508712 43.8482697,42.8403156 C43.8671586,42.8630934 43.7366031,42.9292045 43.7143808,42.9419823 C43.6966031,42.9519823 43.675492,42.9514268 43.6582697,42.9608712 C43.6338253,42.973649 43.6193808,42.9975379 43.5966031,43.0125379 C43.5382697,43.0503156 43.4882697,43.098649 43.4449364,43.1525379 C43.4132697,43.1919823 43.3910475,43.2425379 43.3577142,43.2792045 C43.3632697,43.2408712 43.355492,43.2130934 43.3566031,43.1758712 C43.3127142,43.2030934 43.2949364,43.2508712 43.2343808,43.2369823 C43.1799364,43.2247601 43.1360475,43.2803156 43.0966031,43.3103156 C43.005492,43.3797601 42.9443808,43.4569823 42.8677142,43.538649 C42.825492,43.5842045 42.7799364,43.6164268 42.7532697,43.673649 C42.7249364,43.7342045 42.6843808,43.7892045 42.6466031,43.8453156 C42.5766031,43.9492045 42.4949364,44.0447601 42.4243808,44.148649 C42.2816031,44.3614268 42.1871586,44.6058712 42.0699364,44.8319823 C42.0082697,44.9497601 41.9499364,45.0664268 41.9260475,45.1969823 C41.9043808,45.3108712 41.9032697,45.4269823 41.9066031,45.5419823 C41.970492,45.4914268 41.9671586,45.5975379 41.9510475,45.6280934 C41.9260475,45.6764268 41.9171586,45.7308712 41.9088253,45.783649 C41.8982697,45.853649 41.8860475,45.923649 41.8860475,45.9942045 C41.8866031,46.053649 41.8671586,46.1097601 41.865492,46.1675379 C41.8427142,46.1508712 41.8916031,46.0780934 41.8488253,46.0880934 C41.8277142,46.0930934 41.8266031,46.1247601 41.8216031,46.1403156 C41.8043808,46.1958712 41.7288253,46.1908712 41.7177142,46.253649 C41.710492,46.2930934 41.7066031,46.3147601 41.680492,46.3464268 C41.6616031,46.3697601 41.6799364,46.3803156 41.6838253,46.403649 C41.6938253,46.4603156 41.6216031,46.5369823 41.6410475,46.5819823 C41.6593808,46.6253156 41.6460475,46.6747601 41.6660475,46.7164268 C41.6766031,46.7380934 41.7010475,46.7658712 41.6910475,46.7925379 C41.6466031,46.8014268 41.7043808,46.9058712 41.7082697,46.9380934 C41.7149364,46.9908712 41.7610475,47.1553156 41.8127142,47.178649 C41.8760475,47.2769823 41.9627142,47.4125379 42.0749364,47.4614268 C42.1549364,47.4958712 42.1838253,47.393649 42.2282697,47.3503156 C42.2849364,47.2947601 42.3571586,47.2597601 42.4321586,47.233649 C42.4949364,47.2114268 42.7560475,47.1353156 42.630492,47.0453156 Z M48.0699364,48.1330934 C48.0499364,48.1330934 48.0143808,48.1469823 47.9982697,48.1608712 C47.9660475,48.1880934 48.0343808,48.2047601 48.0610475,48.2125379 C48.090492,48.2292045 48.1321586,48.2380934 48.1616031,48.2547601 C48.1860475,48.2725379 48.2027142,48.2975379 48.230492,48.3097601 C48.2643808,48.3253156 48.310492,48.3330934 48.3477142,48.3430934 C48.3643808,48.348649 48.3882697,48.3469823 48.4093808,48.3514268 C48.4327142,48.3647601 48.4432697,48.3864268 48.4621586,48.4008712 C48.4949364,48.4314268 48.5416031,48.438649 48.585492,48.4375379 C48.6266031,48.4414268 48.6577142,48.4480934 48.6949364,48.438649 C48.7366031,48.4280934 48.7671586,48.4497601 48.8066031,48.4492045 C48.8216031,48.4503156 48.8377142,48.4364268 48.8527142,48.4375379 C48.8727142,48.4375379 48.8749364,48.4464268 48.8843808,48.4630934 C48.9010475,48.4880934 48.945492,48.5253156 48.975492,48.5275379 C48.9949364,48.5269823 49.0093808,48.523649 49.025492,48.5292045 C49.0427142,48.5392045 49.050492,48.5397601 49.0632697,48.5508712 C49.085492,48.5597601 49.1043808,48.5669823 49.1143808,48.583649 C49.1316031,48.6130934 49.1310475,48.6469823 49.1566031,48.6697601 C49.175492,48.6842045 49.1938253,48.6992045 49.2127142,48.713649 C49.225492,48.7247601 49.2149364,48.7225379 49.2343808,48.7225379 C49.245492,48.7247601 49.2649364,48.7242045 49.2788253,48.7208712 C49.3321586,48.7169823 49.3027142,48.6425379 49.2866031,48.6175379 C49.275492,48.5958712 49.2660475,48.5792045 49.2710475,48.558649 C49.2738253,48.533649 49.2849364,48.5169823 49.2693808,48.4964268 C49.2610475,48.4842045 49.2493808,48.4775379 49.2377142,48.4708712 C49.2310475,48.4630934 49.2282697,48.453649 49.220492,48.4414268 C49.2049364,48.4208712 49.1738253,48.4147601 49.1538253,48.3953156 C49.1193808,48.3603156 49.1021586,48.3119823 49.0588253,48.2792045 C49.035492,48.2658712 49.0132697,48.2764268 48.985492,48.2642045 C48.9738253,48.2575379 48.9671586,48.2497601 48.950492,48.2442045 C48.9343808,48.238649 48.9199364,48.2419823 48.905492,48.2408712 C48.8749364,48.238649 48.8482697,48.2119823 48.8188253,48.2147601 C48.785492,48.2180934 48.7799364,48.2530934 48.7599364,48.2725379 C48.7438253,48.2864268 48.7238253,48.2864268 48.7182697,48.2642045 C48.715492,48.2358712 48.7260475,48.218649 48.7410475,48.2003156 C48.7643808,48.1753156 48.7410475,48.1619823 48.710492,48.1597601 C48.6710475,48.1603156 48.6643808,48.1908712 48.6482697,48.223649 C48.6227142,48.2592045 48.6060475,48.2342045 48.570492,48.228649 C48.5460475,48.2303156 48.5288253,48.2397601 48.5066031,48.2303156 C48.4899364,48.2253156 48.4866031,48.2114268 48.4749364,48.2047601 C48.4577142,48.1947601 48.4432697,48.1980934 48.430492,48.2064268 C48.4121586,48.2108712 48.4121586,48.2108712 48.3943808,48.2008712 C48.3782697,48.1953156 48.3749364,48.1814268 48.3538253,48.1775379 C48.3227142,48.1708712 48.2899364,48.1980934 48.2632697,48.1903156 C48.2516031,48.183649 48.2421586,48.1669823 48.2260475,48.1614268 C48.2082697,48.1514268 48.2110475,48.1603156 48.1993808,48.1730934 C48.1799364,48.1925379 48.1516031,48.1992045 48.1327142,48.1847601 C48.1088253,48.1669823 48.1060475,48.138649 48.0699364,48.1330934 Z M48.8121586,48.8430934 C48.805492,48.8297601 48.8127142,48.8164268 48.8127142,48.803649 C48.8093808,48.7803156 48.8027142,48.773649 48.8066031,48.7508712 C48.8132697,48.7375379 48.8132697,48.7175379 48.8099364,48.7008712 C48.8038253,48.6875379 48.7938253,48.6780934 48.7832697,48.6680934 C48.7838253,48.6614268 48.780492,48.6514268 48.7738253,48.6447601 C48.7599364,48.6314268 48.7466031,48.6508712 48.7327142,48.6575379 C48.7227142,48.6675379 48.7021586,48.673649 48.6982697,48.683649 C48.6882697,48.7003156 48.6949364,48.713649 48.6949364,48.7269823 L48.6977142,48.7430934 C48.6738253,48.7664268 48.6971586,48.8292045 48.6932697,48.8525379 C48.6932697,48.878649 48.655492,48.9542045 48.7066031,48.9314268 C48.7199364,48.9247601 48.730492,48.9153156 48.7438253,48.908649 C48.7610475,48.898649 48.7810475,48.8992045 48.8016031,48.8925379 C48.8082697,48.8925379 48.845492,48.8897601 48.845492,48.8830934 C48.8460475,48.8697601 48.815492,48.8597601 48.8121586,48.8430934 Z M49.2532697,49.4003156 C49.2616031,49.3942045 49.2743808,49.4003156 49.2810475,49.3980934 C49.2982697,49.3953156 49.3066031,49.378649 49.3216031,49.3808712 C49.3388253,49.3780934 49.3299364,49.3947601 49.3410475,49.4053156 C49.3521586,49.4153156 49.3627142,49.4153156 49.3732697,49.4153156 C49.3927142,49.4192045 49.4293808,49.4203156 49.4371586,49.403649 C49.445492,49.3764268 49.400492,49.3708712 49.3899364,49.3497601 C49.3782697,49.3192045 49.4032697,49.2892045 49.4116031,49.2619823 C49.4238253,49.2258712 49.3788253,49.2103156 49.3827142,49.1808712 C49.3816031,49.1492045 49.4032697,49.138649 49.3960475,49.1097601 C49.3916031,49.0869823 49.3716031,49.0619823 49.3588253,49.0453156 C49.345492,49.0292045 49.3216031,49.0125379 49.3232697,48.9875379 C49.3249364,48.9625379 49.3721586,48.963649 49.350492,48.9325379 C49.3371586,48.9058712 49.3027142,48.9108712 49.2727142,48.9069823 C49.2621586,48.9069823 49.2516031,48.9075379 49.240492,48.8969823 C49.2316031,48.8825379 49.235492,48.8742045 49.235492,48.863649 C49.2288253,48.8347601 49.2071586,48.8247601 49.1810475,48.8125379 C49.1721586,48.808649 49.1599364,48.8025379 49.1549364,48.7903156 C49.150492,48.7775379 49.1632697,48.7730934 49.1588253,48.7608712 C49.145492,48.733649 49.0949364,48.7725379 49.0732697,48.7619823 C49.0582697,48.7603156 49.0599364,48.7458712 49.0510475,48.7308712 L49.025492,48.7192045 C48.990492,48.7030934 48.9760475,48.7325379 48.9832697,48.7614268 C48.9993808,48.8258712 49.0471586,48.8692045 49.0416031,48.9342045 C49.0443808,48.9614268 49.0488253,48.973649 49.0577142,48.998649 C49.0671586,49.0342045 49.0766031,49.0514268 49.060492,49.0853156 C49.0349364,49.1042045 49.0577142,49.1280934 49.0671586,49.1530934 C49.0716031,49.1864268 49.080492,49.2114268 49.0788253,49.2469823 C49.0738253,49.3119823 49.0532697,49.3747601 49.0588253,49.4397601 C49.0616031,49.4664268 49.0593808,49.4919823 49.0688253,49.5169823 C49.0738253,49.5497601 49.0993808,49.5619823 49.1277142,49.5803156 C49.1538253,49.6030934 49.2771586,49.6758712 49.2410475,49.5869823 C49.2299364,49.5658712 49.2127142,49.5369823 49.2077142,49.5142045 C49.1988253,49.4892045 49.2288253,49.4719823 49.230492,49.4469823 C49.2338253,49.4175379 49.2127142,49.4075379 49.2532697,49.4003156 Z M48.2932697,41.9630934 C48.3249364,41.9964268 48.3632697,42.0064268 48.3560475,42.0603156 C48.3971586,42.0653156 48.4227142,42.0808712 48.4460475,42.0464268 C48.4599364,42.0247601 48.4827142,42.0080934 48.5066031,41.9992045 C48.5366031,41.9875379 48.6588253,41.988649 48.6532697,42.0380934 C48.650492,42.0630934 48.6349364,42.0853156 48.6310475,42.1092045 C48.6260475,42.1430934 48.6621586,42.118649 48.6793808,42.1275379 C48.660492,42.1397601 48.6382697,42.1480934 48.6160475,42.153649 C48.6260475,42.1592045 48.6316031,42.1680934 48.6332697,42.1792045 C48.605492,42.1858712 48.5916031,42.2625379 48.5432697,42.2775379 C48.5132697,42.2869823 48.470492,42.2669823 48.440492,42.2630934 C48.405492,42.258649 48.3788253,42.248649 48.3438253,42.2464268 C48.3093808,42.2442045 48.3371586,42.1997601 48.2949364,42.2080934 C48.2871586,42.2375379 48.3027142,42.3142045 48.3071586,42.343649 C48.3132697,42.3814268 48.3443808,42.4030934 48.3816031,42.4092045 C48.4332697,42.4175379 48.4582697,42.4347601 48.5027142,42.4597601 C48.5377142,42.4797601 48.5771586,42.4680934 48.615492,42.4714268 C48.6399364,42.473649 48.6616031,42.483649 48.680492,42.498649 C48.6766031,42.5108712 48.6677142,42.5303156 48.6732697,42.5430934 C48.6810475,42.5597601 48.7349364,42.5403156 48.7482697,42.5392045 C48.7860475,42.5353156 48.8216031,42.493649 48.8577142,42.4992045 C48.8716031,42.5014268 48.935492,42.5208712 48.9327142,42.5364268 C48.8982697,42.5225379 48.8760475,42.5647601 48.8499364,42.5414268 C48.8260475,42.5208712 48.7632697,42.5392045 48.8038253,42.5675379 C48.8077142,42.5703156 48.8182697,42.6508712 48.8171586,42.6592045 C48.8143808,42.6897601 48.760492,42.7214268 48.7643808,42.7408712 C48.7716031,42.7419823 48.820492,42.7458712 48.8316031,42.7547601 C48.8349364,42.7414268 48.8249364,42.7364268 48.8549364,42.7280934 C48.8771586,42.7214268 48.9032697,42.7192045 48.925492,42.7297601 C48.9349364,42.7669823 48.9071586,42.8058712 48.9588253,42.7964268 C49.0077142,42.7869823 49.0288253,42.818649 49.0799364,42.7858712 C49.1110475,42.7664268 49.1460475,42.7703156 49.1732697,42.7969823 C49.2099364,42.8342045 49.1349364,42.8842045 49.1777142,42.9192045 C49.1949364,42.933649 49.2088253,42.9764268 49.225492,42.9842045 C49.2371586,42.9892045 49.3010475,42.9669823 49.3127142,42.9608712 C49.3327142,42.9980934 49.3510475,42.9425379 49.3677142,42.938649 C49.3727142,42.9158712 49.4027142,42.8880934 49.4316031,42.8853156 C49.4743808,42.8814268 49.4749364,42.888649 49.5038253,42.908649 C49.5871586,42.9669823 49.575492,42.8253156 49.6193808,42.7892045 C49.6982697,42.7247601 49.7371586,42.6642045 49.7910475,42.5808712 C49.8338253,42.5153156 49.8927142,42.498649 49.9677142,42.4869823 C50.0277142,42.4775379 50.1182697,42.463649 50.1427142,42.3997601 C50.1716031,42.3247601 50.1032697,42.283649 50.0421586,42.2614268 C49.9743808,42.2375379 49.8977142,42.2119823 49.9277142,42.1264268 C49.9627142,42.0269823 49.9321586,41.9692045 49.8266031,41.9369823 C49.6027142,41.8675379 49.4010475,41.7503156 49.1732697,41.6864268 C48.9716031,41.6303156 48.7682697,41.6092045 48.5610475,41.5980934 C48.4688253,41.5664268 48.2743808,41.5630934 48.2177142,41.6414268 C48.1810475,41.6919823 48.2277142,41.7358712 48.2232697,41.788649 C48.2177142,41.8514268 48.2488253,41.9158712 48.2932697,41.9630934 Z M47.8549364,50.1964268 C47.8977142,50.2175379 47.9710475,50.1764268 48.0121586,50.1630934 C48.0627142,50.1475379 48.1432697,50.0942045 48.195492,50.123649 C48.2166031,50.1353156 48.2266031,50.1597601 48.2499364,50.1697601 C48.2771586,50.1814268 48.3099364,50.1708712 48.3371586,50.1647601 C48.3660475,50.1580934 48.3982697,50.153649 48.4249364,50.1408712 C48.4482697,50.1297601 48.4632697,50.1103156 48.4832697,50.0947601 C48.5360475,50.0558712 48.580492,50.0925379 48.6377142,50.0842045 C48.6699364,50.0797601 48.700492,50.063649 48.7327142,50.0558712 C48.7560475,50.0497601 48.7971586,50.0503156 48.815492,50.0330934 C48.8360475,50.013649 48.8266031,49.9719823 48.8260475,49.9475379 C48.8249364,49.9142045 48.8277142,49.8803156 48.815492,49.848649 C48.7921586,49.7875379 48.7077142,49.7158712 48.7949364,49.6680934 C48.8121586,49.5608712 48.6877142,49.5792045 48.6516031,49.5019823 C48.6282697,49.4514268 48.620492,49.4130934 48.5538253,49.4075379 C48.4966031,49.4025379 48.4638253,49.4319823 48.4143808,49.4514268 C48.3593808,49.4725379 48.3171586,49.4542045 48.2693808,49.4269823 C48.2410475,49.4108712 48.1799364,49.373649 48.1682697,49.4230934 C48.1582697,49.4658712 48.1988253,49.5075379 48.1649364,49.5458712 C48.135492,49.5797601 48.0838253,49.5953156 48.0410475,49.6047601 C47.9488253,49.6242045 47.8766031,49.6942045 47.8099364,49.7553156 L47.8166031,49.7619823 C47.790492,49.7614268 47.750492,49.8314268 47.7493808,49.8530934 C47.7599364,49.8558712 47.770492,49.8592045 47.7816031,49.8625379 C47.7799364,49.8992045 47.8221586,49.8747601 47.8243808,49.848649 C47.8332697,49.8503156 47.8421586,49.8558712 47.850492,49.8575379 C47.8577142,49.8592045 47.8738253,49.858649 47.8810475,49.8614268 C47.8982697,49.8692045 47.9016031,49.8864268 47.9243808,49.888649 C47.9110475,49.9442045 47.9227142,50.0030934 47.895492,50.0547601 C47.8788253,50.0875379 47.7927142,50.1669823 47.8549364,50.1964268 Z M42.7410475,50.5614268 C42.7532697,50.5408712 42.7432697,50.4858712 42.720492,50.4675379 C42.6638253,50.4208712 42.6421586,50.5319823 42.6777142,50.5647601 C42.6916031,50.5997601 42.7249364,50.588649 42.7410475,50.5614268 Z M45.9971586,42.3703156 C45.9777142,42.3692045 45.9566031,42.3708712 45.9388253,42.378649 C45.9343808,42.3803156 45.9288253,42.3847601 45.9249364,42.3892045 C45.9316031,42.3903156 45.9382697,42.3903156 45.9443808,42.388649 C45.9632697,42.3847601 45.9777142,42.3714268 45.9971586,42.3703156 Z M48.0010475,41.6769823 C48.0016031,41.6792045 48.0021586,41.6819823 48.0027142,41.6842045 C48.035492,41.6769823 48.0677142,41.6869823 48.0988253,41.6747601 C48.1149364,41.6680934 48.1660475,41.6497601 48.1632697,41.628649 C48.1577142,41.5892045 47.9893808,41.6147601 47.9621586,41.6264268 C47.9532697,41.6508712 47.9793808,41.6703156 48.0010475,41.6769823 Z M45.9310475,42.053649 C45.9727142,42.0225379 45.995492,41.9653156 46.0621586,41.9769823 C46.0599364,42.0342045 46.1160475,42.0414268 46.1571586,42.0630934 C46.1443808,42.0969823 46.0982697,42.0958712 46.0777142,42.1225379 C46.0521586,42.1553156 46.0982697,42.1847601 46.1216031,42.1992045 C46.1671586,42.2275379 46.1427142,42.258649 46.1332697,42.2992045 C46.120492,42.3547601 46.2410475,42.3397601 46.2671586,42.338649 C46.3132697,42.3358712 46.385492,42.3442045 46.4282697,42.3219823 C46.475492,42.2969823 46.4999364,42.2397601 46.5482697,42.2119823 C46.5882697,42.1892045 46.645492,42.1758712 46.690492,42.1919823 C46.7371586,42.2080934 46.7316031,42.2675379 46.7693808,42.293649 C46.8149364,42.3242045 46.8627142,42.3342045 46.9016031,42.2880934 C46.925492,42.258649 46.980492,42.2225379 46.9827142,42.1919823 C46.9866031,42.1375379 47.0021586,42.0942045 47.0632697,42.0825379 C47.1110475,42.0730934 47.1010475,42.1203156 47.1349364,42.128649 C47.2093808,42.1475379 47.245492,41.918649 47.3277142,41.9864268 C47.3471586,42.0025379 47.3527142,42.0642045 47.3849364,42.0592045 C47.4188253,42.053649 47.4199364,42.003649 47.4566031,42.0030934 C47.4682697,42.0369823 47.3932697,42.0775379 47.3832697,42.1130934 C47.4293808,42.0758712 47.4516031,42.0808712 47.5038253,42.0758712 C47.5177142,42.1114268 47.415492,42.168649 47.3893808,42.1753156 C47.3521586,42.183649 47.3299364,42.1630934 47.3016031,42.1825379 C47.2788253,42.198649 47.2482697,42.1969823 47.2221586,42.1997601 C47.1843808,42.2030934 47.1149364,42.2525379 47.115492,42.293649 C47.115492,42.3097601 47.1282697,42.3458712 47.1149364,42.3592045 C47.1016031,42.3725379 47.0716031,42.3597601 47.0677142,42.3458712 C47.035492,42.3930934 46.9949364,42.3108712 46.9682697,42.3769823 C47.0110475,42.3869823 47.0493808,42.428649 47.0977142,42.4403156 C47.1449364,42.4519823 47.190492,42.4630934 47.2371586,42.4769823 C47.3138253,42.5003156 47.4293808,42.4075379 47.4893808,42.3619823 C47.5449364,42.3203156 47.6160475,42.2258712 47.6310475,42.1580934 C47.6477142,42.0853156 47.7288253,42.0003156 47.7121586,41.9269823 C47.6966031,41.8592045 47.6860475,41.8264268 47.7632697,41.8025379 C47.7966031,41.7919823 47.875492,41.7747601 47.8871586,41.7364268 C47.9043808,41.6808712 47.7299364,41.6958712 47.7032697,41.683649 C47.6149364,41.6442045 47.5771586,41.5997601 47.475492,41.6392045 C47.4221586,41.6597601 47.3699364,41.6769823 47.3149364,41.6919823 C47.2860475,41.6997601 47.2571586,41.7019823 47.2416031,41.7258712 C47.235492,41.7358712 47.2271586,41.743649 47.2166031,41.748649 C47.1710475,41.7664268 47.2271586,41.6803156 47.2321586,41.6747601 C47.2471586,41.6580934 47.2716031,41.6053156 47.2238253,41.6164268 C47.1538253,41.6330934 47.1032697,41.7403156 47.0271586,41.7458712 C46.9699364,41.7497601 46.9882697,41.6992045 47.0038253,41.6703156 C47.0316031,41.6180934 46.9499364,41.6119823 46.9138253,41.6114268 C46.8638253,41.6114268 46.8249364,41.6403156 46.7766031,41.6453156 C46.7310475,41.6503156 46.6782697,41.6592045 46.6327142,41.6575379 C46.540492,41.6542045 46.480492,41.7075379 46.3899364,41.6775379 C46.2949364,41.6464268 46.1916031,41.7258712 46.0999364,41.7369823 C46.0693808,41.7408712 46.0243808,41.7342045 46.0121586,41.7708712 C46.0016031,41.8008712 46.0121586,41.8475379 46.035492,41.8697601 L46.0427142,41.863649 C46.0227142,41.8830934 46.0199364,41.9119823 45.9910475,41.9225379 C45.9621586,41.9330934 45.9332697,41.9703156 45.9182697,41.9953156 C45.9066031,42.0158712 45.8738253,42.0953156 45.9310475,42.053649 Z M46.0216031,43.2903156 C46.045492,43.2597601 45.9877142,43.2325379 45.9543808,43.2358712 C45.9632697,43.1980934 46.010492,43.178649 45.9982697,43.1314268 C45.9860475,43.0825379 45.9271586,43.0919823 45.895492,43.118649 C45.8660475,43.143649 45.8510475,43.1864268 45.8266031,43.2147601 C45.8132697,43.2314268 45.7882697,43.2364268 45.7788253,43.2564268 C45.7699364,43.2747601 45.780492,43.3053156 45.7799364,43.3253156 C45.8188253,43.3303156 45.860492,43.318649 45.8888253,43.2908712 L45.9077142,43.2819823 C45.9038253,43.2858712 45.9010475,43.293649 45.8993808,43.2975379 C45.9182697,43.3214268 46.0032697,43.3147601 46.0216031,43.2903156 Z M43.5977142,52.0842045 C43.6038253,52.0658712 43.575492,52.0453156 43.5560475,52.0469823 C43.5388253,52.0480934 43.5232697,52.0708712 43.5160475,52.083649 C43.4960475,52.1214268 43.5193808,52.1719823 43.5682697,52.1719823 C43.5793808,52.1514268 43.5749364,52.1114268 43.605492,52.108649 C43.6038253,52.0992045 43.5971586,52.0958712 43.5882697,52.0925379 L43.5977142,52.0842045 Z M53.2532697,50.4553156 C53.2510475,50.4464268 53.2488253,50.4369823 53.2471586,50.4275379 C53.2038253,50.4147601 53.175492,50.4603156 53.135492,50.4269823 C53.0582697,50.478649 53.1410475,50.583649 53.0138253,50.5764268 C53.035492,50.6030934 53.0332697,50.633649 53.0232697,50.6642045 C53.0066031,50.7130934 52.9949364,50.708649 52.9616031,50.7142045 C52.8932697,50.7258712 52.8593808,50.6825379 52.8388253,50.6253156 C52.7710475,50.6264268 52.6777142,50.7319823 52.6238253,50.7647601 C52.6110475,50.7730934 52.5860475,50.7953156 52.5710475,50.8042045 C52.5593808,50.8114268 52.5310475,50.8253156 52.5160475,50.8330934 C52.480492,50.8519823 52.4027142,50.8764268 52.3982697,50.9169823 C52.380492,50.9142045 52.3527142,50.9247601 52.335492,50.9225379 C52.3293808,50.9314268 52.3293808,50.9408712 52.335492,50.9497601 C52.4177142,50.9642045 52.4610475,50.9364268 52.530492,50.9058712 C52.6043808,50.873649 52.6832697,50.8803156 52.7516031,50.8547601 C52.7843808,50.8430934 52.785492,50.8064268 52.8393808,50.828649 C52.8627142,50.838649 52.8910475,50.8730934 52.8960475,50.8969823 C52.9066031,50.9503156 52.850492,51.0292045 52.7943808,51.0330934 C52.7810475,50.9997601 52.7999364,50.9658712 52.8060475,50.938649 C52.7327142,50.913649 52.610492,51.018649 52.5899364,51.0808712 C52.6671586,51.0964268 52.6982697,51.208649 52.6577142,51.2692045 C52.6438253,51.2842045 52.6282697,51.303649 52.6010475,51.3119823 C52.5577142,51.3247601 52.5371586,51.2847601 52.4949364,51.3164268 C52.4393808,51.3580934 52.500492,51.4725379 52.4688253,51.5369823 C52.4443808,51.5858712 52.4027142,51.6042045 52.3682697,51.638649 C52.3443808,51.6630934 52.3310475,51.6897601 52.2988253,51.7114268 C52.2571586,51.7392045 52.1566031,51.798649 52.165492,51.8569823 C52.2560475,51.8875379 52.4449364,51.7292045 52.5238253,51.6769823 C52.5738253,51.643649 52.6038253,51.5919823 52.6549364,51.5597601 C52.7121586,51.523649 52.7877142,51.5058712 52.8249364,51.4419823 C52.8460475,51.4064268 52.8293808,51.3742045 52.8421586,51.3380934 C52.8532697,51.3058712 52.8760475,51.2953156 52.8971586,51.2703156 C52.9360475,51.2230934 52.9727142,51.208649 53.0171586,51.1692045 C53.0721586,51.1203156 53.0593808,51.0430934 53.0843808,50.9780934 C53.1066031,50.9208712 53.150492,50.8780934 53.180492,50.8247601 C53.2288253,50.7392045 53.3538253,50.5375379 53.3016031,50.4430934 C53.2882697,50.4530934 53.2643808,50.4508712 53.2532697,50.4553156 Z M51.8121586,51.8869823 L51.810492,51.8853156 C51.8138253,51.8914268 51.8127142,51.9003156 51.8132697,51.9080934 C51.8549364,51.9080934 51.8727142,51.9453156 51.9171586,51.9319823 C51.9632697,51.918649 51.9888253,51.8753156 51.9527142,51.8392045 C51.9216031,51.8075379 51.8938253,51.7814268 51.8466031,51.7897601 C51.7921586,51.8003156 51.8043808,51.8442045 51.8121586,51.8869823 Z M42.780492,47.3764268 C42.7943808,47.3864268 42.8127142,47.3953156 42.8266031,47.4047601 C42.840492,47.4147601 42.8621586,47.4319823 42.8788253,47.4364268 C42.9177142,47.4575379 42.9777142,47.4814268 43.0066031,47.4364268 C43.015492,47.4180934 43.0210475,47.4064268 43.010492,47.3908712 C42.9988253,47.3747601 42.9821586,47.3703156 42.9777142,47.3575379 C42.9727142,47.3442045 42.9843808,47.3347601 42.970492,47.3247601 C42.9549364,47.3108712 42.9432697,47.3197601 42.9421586,47.2903156 C42.9432697,47.2653156 42.9449364,47.2147601 42.9110475,47.2469823 C42.9021586,47.2497601 42.9143808,47.2553156 42.900492,47.2603156 C42.8916031,47.263649 42.8838253,47.2564268 42.8777142,47.2530934 C42.8593808,47.2453156 42.8482697,47.243649 42.835492,47.2630934 C42.8249364,47.2769823 42.825492,47.2914268 42.8088253,47.3025379 L42.7821586,47.3114268 C42.7727142,47.3147601 42.7443808,47.3347601 42.7432697,47.3447601 C42.7388253,47.3614268 42.7638253,47.3725379 42.780492,47.3764268 Z M52.1093808,51.7808712 C52.115492,51.7503156 52.1788253,51.6397601 52.1199364,51.623649 C52.0988253,51.6175379 52.080492,51.6503156 52.0610475,51.6558712 C52.0360475,51.663649 52.0088253,51.6475379 51.9849364,51.6603156 C51.9632697,51.6714268 51.9438253,51.7058712 51.9310475,51.7253156 C51.9149364,51.7492045 51.9210475,51.7580934 51.9466031,51.7714268 C51.9727142,51.7853156 52.005492,51.7919823 52.0199364,51.8197601 C52.0332697,51.8447601 52.0266031,51.8769823 52.0227142,51.9030934 C52.0238253,51.9014268 52.0271586,51.8980934 52.0282697,51.893649 C52.0327142,51.8925379 52.0399364,51.8914268 52.0443808,51.8925379 L52.0388253,51.903649 C52.1071586,51.9130934 52.1016031,51.8253156 52.1093808,51.7808712 Z M54.3488253,48.1230934 C54.3282697,48.0014268 54.3982697,47.8325379 54.335492,47.7158712 C54.3082697,47.6647601 54.3499364,47.5169823 54.3493808,47.4564268 C54.3482697,47.3442045 54.3149364,47.258649 54.2993808,47.153649 C54.2888253,47.0558712 54.2760475,46.7947601 54.3116031,46.7058712 C54.3621586,46.5803156 54.1143808,46.3680934 54.100492,46.2325379 C54.0882697,46.1153156 54.0210475,46.003649 53.9249364,45.9358712 C53.885492,45.9075379 53.800492,45.5192045 53.750492,45.5369823 C53.725492,45.548649 53.7771586,45.6342045 53.7738253,45.6608712 C53.760492,45.7558712 53.7127142,45.6614268 53.6621586,45.6825379 C53.570492,45.7203156 53.4732697,45.8080934 53.4610475,45.8997601 C53.4171586,46.2369823 53.1782697,45.888649 53.1971586,45.8742045 C53.250492,45.8330934 53.2693808,45.8458712 53.3310475,45.8369823 C53.400492,45.8125379 53.2932697,45.763649 53.4021586,45.7530934 C53.3766031,45.6842045 53.4332697,45.6614268 53.405492,45.6064268 C53.3643808,45.5264268 53.3349364,45.5358712 53.3766031,45.4497601 C53.3943808,45.4019823 53.2866031,45.253649 53.2777142,45.1964268 C53.2688253,45.1408712 53.2677142,45.068649 53.260492,45.0064268 C53.2560475,44.9669823 53.3210475,44.928649 53.3099364,44.8980934 C53.3071586,44.7875379 53.3316031,44.6680934 53.2927142,44.5619823 C53.265492,44.488649 53.2338253,44.3930934 53.1871586,44.3308712 C53.170492,44.308649 53.120492,44.1980934 53.1160475,44.1680934 C53.1027142,44.0992045 53.0721586,44.1253156 53.0332697,44.0975379 C53.0121586,44.0714268 52.9166031,43.9825379 52.8888253,43.9697601 C52.8638253,43.9580934 52.6793808,43.7897601 52.6749364,43.7764268 C52.6599364,43.7275379 52.5560475,43.6903156 52.5671586,43.6380934 C52.5843808,43.5580934 52.3071586,43.3525379 52.2299364,43.3397601 C52.1799364,43.3308712 52.3827142,43.5719823 52.3816031,43.5675379 C52.3849364,43.5814268 52.5121586,43.7425379 52.5116031,43.7425379 C52.5410475,43.7525379 52.610492,43.9503156 52.6082697,43.9758712 C52.600492,44.0514268 52.4066031,43.8530934 52.3921586,43.8264268 C52.2971586,43.7092045 52.1232697,43.6153156 52.0143808,43.5369823 C51.9371586,43.4658712 51.975492,43.4247601 51.8432697,43.3642045 C51.7938253,43.3414268 51.6616031,43.2325379 51.620492,43.2308712 C51.570492,43.2275379 51.6271586,43.3330934 51.6288253,43.3447601 C51.6366031,43.4197601 51.7182697,43.4253156 51.7666031,43.4747601 C51.8060475,43.5147601 51.840492,43.5653156 51.8066031,43.6097601 C51.8060475,43.6103156 51.745492,43.7214268 51.7421586,43.7114268 C51.7593808,43.758649 51.8999364,43.8630934 51.9349364,43.9019823 C51.9288253,43.8914268 52.1199364,44.1408712 52.1360475,44.008649 C52.1421586,43.9608712 52.0882697,43.9025379 52.095492,43.8614268 C52.100492,43.8347601 52.3138253,44.103649 52.3249364,44.1275379 C52.3777142,44.2697601 52.3732697,44.1058712 52.4271586,44.1225379 C52.4721586,44.1369823 52.5916031,44.2853156 52.4927142,44.2914268 C52.3399364,44.3019823 52.5199364,44.433649 52.5621586,44.453649 C52.650492,44.4964268 52.7099364,44.5958712 52.8021586,44.6330934 C52.9449364,44.6914268 52.9149364,44.7942045 52.9877142,44.8992045 C53.010492,44.9325379 52.5760475,44.898649 52.5416031,44.918649 C52.4843808,44.9625379 52.7599364,45.2708712 52.7616031,45.3169823 C52.7638253,45.4130934 52.8182697,45.483649 52.8327142,45.5803156 C52.8421586,45.6692045 52.8310475,45.7858712 52.8893808,45.8575379 C52.9371586,45.9014268 52.9816031,45.7847601 53.0566031,45.853649 C53.0843808,45.8664268 53.1238253,45.8975379 53.1327142,45.9214268 C53.1571586,45.9875379 53.3016031,46.393649 53.1288253,46.3630934 C53.0527142,46.3497601 53.0960475,46.6819823 53.1010475,46.7303156 C53.125492,46.8353156 53.1671586,46.8275379 53.1399364,46.9597601 C53.1427142,47.0703156 53.0532697,47.1258712 52.990492,47.2047601 C52.9610475,47.2408712 52.9416031,47.283649 52.9271586,47.3297601 C52.8921586,47.2958712 52.875492,47.2414268 52.8271586,47.2230934 C52.7738253,47.2030934 52.6571586,47.2664268 52.610492,47.2897601 C52.4977142,47.3458712 52.580492,47.448649 52.5366031,47.5375379 C52.5049364,47.6025379 52.4088253,47.6347601 52.3471586,47.6669823 C52.2716031,47.7053156 52.1710475,47.7464268 52.0960475,47.6847601 C52.0327142,47.6325379 52.060492,47.5292045 51.9932697,47.4842045 C51.9177142,47.433649 51.9110475,47.5464268 51.8938253,47.5908712 C51.8610475,47.6758712 51.7627142,47.7047601 51.7877142,47.8108712 C51.7982697,47.8542045 51.820492,47.893649 51.8293808,47.9375379 C51.8399364,47.9925379 51.8127142,48.0419823 51.8088253,48.0964268 C51.8032697,48.1892045 51.8999364,48.2108712 51.9260475,48.2875379 C51.9482697,48.3547601 51.9249364,48.4614268 51.8482697,48.4875379 C51.7666031,48.5158712 51.6766031,48.4419823 51.5960475,48.433649 C51.5149364,48.4253156 51.4127142,48.4480934 51.3971586,48.5397601 C51.3832697,48.6214268 51.4666031,48.6875379 51.4266031,48.7708712 C51.4093808,48.8058712 51.3799364,48.833649 51.3577142,48.8658712 C51.3210475,48.9192045 51.300492,48.9819823 51.265492,49.0364268 C51.3066031,49.0375379 51.3027142,49.0119823 51.3393808,49.0197601 C51.3788253,49.0275379 51.4132697,48.988649 51.4466031,48.9753156 C51.4521586,49.0008712 51.4493808,49.0275379 51.4527142,49.053649 C51.4799364,49.0619823 51.5077142,49.0508712 51.5321586,49.0414268 C51.535492,49.0642045 51.5249364,49.0903156 51.5343808,49.1125379 C51.5421586,49.1319823 51.5638253,49.1380934 51.5766031,49.1530934 C51.6099364,49.1930934 51.5693808,49.2580934 51.5460475,49.2919823 C51.4788253,49.3903156 51.3649364,49.4453156 51.290492,49.5369823 C51.220492,49.6230934 51.2138253,49.7264268 51.1621586,49.8197601 C51.1443808,49.8514268 51.1260475,49.8953156 51.1743808,49.9108712 C51.1838253,49.893649 51.2010475,49.8797601 51.2221586,49.8797601 C51.255492,49.8792045 51.2432697,49.9030934 51.2593808,49.9230934 C51.3288253,50.0103156 51.4021586,49.8653156 51.4310475,49.823649 C51.4616031,49.7797601 51.5843808,49.7119823 51.6188253,49.7869823 C51.6449364,49.843649 51.6143808,49.9242045 51.5877142,49.9753156 C51.6316031,49.9953156 51.6193808,50.0275379 51.6293808,50.0658712 C51.6432697,50.1192045 51.6927142,50.153649 51.6927142,50.2119823 C51.6927142,50.2830934 51.5377142,50.4242045 51.5977142,50.4769823 C51.6732697,50.5430934 51.7627142,50.3597601 51.7916031,50.3203156 C51.8482697,50.2442045 51.9766031,50.233649 52.0077142,50.1397601 C52.0432697,50.0342045 52.0310475,49.9714268 52.1732697,49.9680934 C52.2327142,49.9669823 52.275492,49.928649 52.3316031,49.9175379 C52.3932697,49.9053156 52.420492,49.898649 52.4599364,49.8514268 C52.5166031,49.7847601 52.5666031,49.8647601 52.5693808,49.9180934 C52.5716031,49.9730934 52.5471586,50.0419823 52.580492,50.0903156 C52.6221586,50.1508712 52.6682697,50.0708712 52.7099364,50.0330934 C52.7060475,50.0730934 52.760492,50.0969823 52.7916031,50.1097601 C52.8410475,50.0764268 52.8716031,50.0219823 52.9238253,49.9919823 C52.9482697,49.978649 52.975492,49.9730934 53.0027142,49.9680934 C53.010492,50.0119823 53.0177142,50.0592045 53.0571586,50.0808712 C53.1132697,50.1130934 53.0443808,50.1442045 53.1182697,50.178649 C53.2110475,50.213649 53.2416031,50.3069823 53.2866031,50.3819823 C53.3071586,50.4158712 53.4999364,50.2080934 53.5749364,50.1997601 C53.820492,50.1719823 53.9482697,49.8525379 54.0399364,49.6625379 C54.1716031,49.3919823 54.230492,49.0908712 54.2649364,48.8108712 C54.3488253,48.6392045 54.3827142,48.323649 54.3488253,48.1230934 Z M52.9038253,50.4425379 C52.9049364,50.4747601 52.9399364,50.4703156 52.9643808,50.4625379 C52.9849364,50.4558712 52.9988253,50.4375379 53.0121586,50.4208712 C53.0316031,50.3969823 53.0427142,50.3719823 53.0266031,50.343649 C53.0088253,50.3130934 52.9982697,50.2914268 52.9899364,50.2564268 C52.9760475,50.2642045 52.9593808,50.2764268 52.9449364,50.2819823 C52.9293808,50.2869823 52.9277142,50.2825379 52.9110475,50.2819823 C52.8716031,50.2797601 52.8793808,50.3103156 52.8627142,50.3347601 C52.8488253,50.3558712 52.8182697,50.3647601 52.8299364,50.3925379 C52.8388253,50.413649 52.8738253,50.4325379 52.8932697,50.4414268 L52.8988253,50.4358712 C52.8977142,50.4369823 52.8966031,50.438649 52.895492,50.4403156 C52.8982697,50.4414268 52.9010475,50.4419823 52.9038253,50.4425379 Z M42.7499364,50.8092045 C42.7449364,50.7747601 42.7316031,50.7530934 42.705492,50.7330934 C42.7043808,50.7358712 42.7032697,50.738649 42.7032697,50.7425379 C42.6677142,50.7247601 42.6627142,50.6564268 42.6149364,50.6614268 C42.5743808,50.5975379 42.4716031,50.6258712 42.440492,50.6853156 C42.4193808,50.7247601 42.4443808,50.7419823 42.4682697,50.7714268 C42.4977142,50.8075379 42.4921586,50.8353156 42.5027142,50.8780934 C42.5293808,50.9858712 42.6193808,50.9114268 42.6832697,50.9542045 C42.7071586,50.9692045 42.7177142,51.0158712 42.7527142,51.0075379 C42.7932697,50.998649 42.7910475,50.938649 42.780492,50.9114268 C42.7660475,50.8730934 42.755492,50.8508712 42.7499364,50.8092045 Z M42.6960475,50.7253156 C42.6993808,50.7280934 42.7027142,50.7303156 42.705492,50.7330934 C42.7082697,50.7275379 42.7121586,50.723649 42.7143808,50.7164268 L42.6960475,50.7253156 Z M52.7827142,52.1864268 C52.740492,52.2058712 52.665492,52.1958712 52.6321586,52.2269823 C52.6138253,52.243649 52.6188253,52.263649 52.6110475,52.2842045 C52.6010475,52.308649 52.5638253,52.3175379 52.5438253,52.3308712 C52.5116031,52.3519823 52.5010475,52.3992045 52.4666031,52.4114268 C52.4516031,52.3892045 52.4288253,52.3253156 52.3988253,52.3808712 C52.3799364,52.4153156 52.3927142,52.4503156 52.3632697,52.4830934 C52.335492,52.513649 52.3371586,52.5497601 52.3171586,52.5830934 C52.2860475,52.6353156 52.2560475,52.6664268 52.2038253,52.6975379 C52.1616031,52.7225379 52.1538253,52.7675379 52.1238253,52.8025379 C52.0916031,52.8392045 52.0438253,52.853649 51.9999364,52.8725379 C51.9693808,52.8858712 51.9182697,52.9225379 51.8838253,52.9014268 C51.8360475,52.8725379 51.900492,52.8214268 51.9227142,52.8030934 C51.9432697,52.7858712 52.0438253,52.7253156 52.0210475,52.6908712 C52.005492,52.668649 51.9471586,52.6714268 51.9243808,52.673649 C51.8771586,52.678649 51.840492,52.7264268 51.800492,52.7497601 C51.755492,52.7753156 51.7160475,52.7969823 51.6666031,52.8142045 C51.6088253,52.8342045 51.6038253,52.8875379 51.5588253,52.9203156 C51.5221586,52.9475379 51.4766031,52.9692045 51.4293808,52.9692045 C51.3677142,52.968649 51.3710475,52.9180934 51.3532697,52.8753156 C51.3299364,52.8780934 51.3093808,52.908649 51.2877142,52.9180934 C51.2510475,52.9330934 51.2282697,52.9492045 51.2432697,52.9897601 C51.2588253,53.0297601 51.0943808,53.0680934 51.0660475,53.0880934 C51.0588253,53.0680934 51.0910475,53.0458712 51.1038253,53.0347601 C51.0527142,53.0303156 50.9960475,53.0742045 50.9443808,53.0814268 C50.8943808,53.088649 50.8327142,53.1219823 50.825492,53.1742045 C50.8199364,53.2142045 50.7649364,53.213649 50.7316031,53.2275379 C50.675492,53.2503156 50.6988253,53.2842045 50.6877142,53.3292045 C50.6666031,53.4142045 50.4899364,53.3492045 50.5799364,53.2369823 C50.6116031,53.1975379 50.6593808,53.1719823 50.6882697,53.1308712 C50.7210475,53.0842045 50.7260475,53.0247601 50.7516031,52.9742045 C50.695492,52.9903156 50.6527142,53.0225379 50.6049364,53.053649 C50.5510475,53.0892045 50.505492,53.0808712 50.445492,53.068649 C50.375492,53.053649 50.3271586,53.0892045 50.2621586,53.1047601 C50.220492,53.1147601 50.1282697,53.1114268 50.1232697,53.1714268 C50.1199364,53.2097601 50.1866031,53.2180934 50.2088253,53.2403156 C50.2421586,53.2742045 50.2771586,53.3247601 50.305492,53.3630934 C50.3282697,53.393649 50.410492,53.4353156 50.405492,53.473649 C50.3960475,53.5480934 50.3027142,53.5353156 50.2510475,53.548649 C50.1999364,53.5614268 50.1927142,53.6069823 50.155492,53.6347601 C50.1138253,53.6653156 50.0566031,53.6297601 50.0093808,53.648649 C49.9616031,53.6675379 49.9277142,53.7092045 49.8860475,53.7380934 C49.8160475,53.7853156 49.7710475,53.7330934 49.6988253,53.7303156 C49.6410475,53.7280934 49.5871586,53.7542045 49.5321586,53.7664268 C49.4821586,53.7775379 49.4227142,53.7858712 49.3832697,53.8214268 C49.300492,53.8958712 49.5699364,53.8764268 49.5943808,53.873649 C49.5832697,53.9169823 49.5721586,53.9669823 49.5327142,53.9953156 C49.4838253,54.0303156 49.4149364,54.0219823 49.3588253,54.0347601 C49.3149364,54.0447601 49.2543808,54.093649 49.3182697,54.1269823 C49.3749364,54.1564268 49.4488253,54.1408712 49.505492,54.1208712 C49.5716031,54.0980934 49.6332697,54.0630934 49.7021586,54.048649 C49.7743808,54.0325379 49.8493808,54.0397601 49.9221586,54.0264268 C49.9993808,54.0125379 50.0671586,53.9697601 50.1388253,53.9408712 C50.2077142,53.9130934 50.2793808,53.9030934 50.3538253,53.9003156 C50.3382697,53.9269823 50.2660475,53.9258712 50.2388253,53.9308712 C50.1843808,53.9419823 50.1477142,53.9892045 50.0910475,53.9864268 C50.0277142,53.983649 50.035492,54.0292045 49.9893808,54.0397601 C49.9599364,54.0464268 49.8893808,54.1153156 49.8671586,54.0730934 C49.8499364,54.0397601 49.8177142,54.0464268 49.8066031,54.0864268 C49.7982697,54.1164268 49.815492,54.1275379 49.7743808,54.1280934 C49.7416031,54.1280934 49.7277142,54.1153156 49.6982697,54.1080934 C49.6416031,54.093649 49.6160475,54.1519823 49.575492,54.1669823 C49.5160475,54.1892045 49.4527142,54.1825379 49.3960475,54.2197601 C49.3627142,54.2414268 49.3266031,54.2475379 49.2877142,54.2592045 C49.2160475,54.2808712 49.1493808,54.308649 49.0788253,54.3325379 C49.0232697,54.3508712 48.9682697,54.3742045 48.9088253,54.3753156 C48.8843808,54.3758712 48.7877142,54.3592045 48.7716031,54.3875379 C48.7416031,54.4408712 48.8327142,54.4208712 48.8521586,54.4103156 C48.9060475,54.3814268 48.9710475,54.3980934 49.0316031,54.3980934 C49.1060475,54.3980934 49.1682697,54.3830934 49.2343808,54.348649 C49.2527142,54.3397601 49.3727142,54.313649 49.3782697,54.3280934 C49.3893808,54.3342045 49.4693808,54.3053156 49.4827142,54.3019823 C49.5471586,54.2875379 49.6116031,54.2730934 49.6749364,54.2569823 C49.8710475,54.2064268 50.0643808,54.1303156 50.2560475,54.0653156 C50.6366031,53.9364268 50.9866031,53.7197601 51.3166031,53.4992045 C51.4666031,53.3992045 51.5882697,53.268649 51.750492,53.1875379 C51.9138253,53.1058712 52.0599364,52.9980934 52.2099364,52.8964268 C52.3566031,52.7964268 52.4677142,52.6597601 52.5893808,52.5342045 C52.7121586,52.4075379 52.8166031,52.2869823 52.8710475,52.1180934 C52.8399364,52.1114268 52.8082697,52.1742045 52.7827142,52.1864268 Z M48.9610475,53.198649 C49.0593808,53.0614268 48.9949364,52.8830934 49.075492,52.7403156 C49.1149364,52.6708712 49.1843808,52.6269823 49.2466031,52.578649 C49.3221586,52.5208712 49.3888253,52.453649 49.4488253,52.3803156 C49.4138253,52.3719823 49.4116031,52.3380934 49.3899364,52.3169823 C49.355492,52.283649 49.3038253,52.3058712 49.2960475,52.2553156 C49.2888253,52.2103156 49.2516031,52.2003156 49.215492,52.1819823 C49.1327142,52.1408712 49.0988253,52.0597601 49.0321586,52.0025379 C48.9582697,51.938649 48.8588253,51.9603156 48.770492,51.9425379 C48.6916031,51.9269823 48.6121586,51.8025379 48.5282697,51.8558712 C48.475492,51.888649 48.450492,51.9742045 48.4866031,52.0258712 C48.5149364,52.0669823 48.5643808,52.0869823 48.5838253,52.1353156 C48.5549364,52.1614268 48.5482697,52.1792045 48.5827142,52.203649 C48.6243808,52.2330934 48.7043808,52.2625379 48.6843808,52.3264268 C48.6732697,52.3603156 48.6449364,52.3930934 48.6077142,52.4003156 C48.580492,52.4053156 48.5160475,52.3897601 48.5299364,52.438649 C48.500492,52.358649 48.4166031,52.4825379 48.3716031,52.4180934 C48.3343808,52.3653156 48.3110475,52.318649 48.2527142,52.2830934 C48.1766031,52.2364268 48.2827142,52.1925379 48.2699364,52.1219823 C48.2510475,52.0203156 48.1271586,52.0480934 48.0821586,51.973649 C48.0566031,51.9303156 48.0927142,51.8964268 48.1093808,51.858649 C48.1260475,51.8203156 48.1721586,51.8508712 48.1960475,51.8625379 C48.275492,51.8997601 48.3927142,51.8864268 48.4593808,51.8308712 C48.490492,51.8053156 48.5582697,51.6864268 48.4716031,51.6875379 C48.4121586,51.6875379 48.3666031,51.7397601 48.310492,51.7430934 C48.3021586,51.6753156 48.2710475,51.5603156 48.3432697,51.5158712 C48.4088253,51.4753156 48.5466031,51.4280934 48.4877142,51.3225379 C48.4671586,51.2864268 48.4299364,51.3464268 48.3993808,51.3203156 C48.3860475,51.308649 48.3949364,51.2842045 48.3999364,51.2725379 C48.3793808,51.253649 48.360492,51.2319823 48.3499364,51.2064268 C48.3032697,51.0930934 48.4410475,51.003649 48.3832697,50.8842045 C48.3588253,50.833649 48.3171586,50.803649 48.2716031,50.7719823 C48.2260475,50.7403156 48.2238253,50.7003156 48.2071586,50.6519823 C48.1977142,50.6247601 48.1538253,50.5619823 48.115492,50.5775379 C48.0827142,50.5908712 48.0727142,50.6469823 48.0482697,50.6708712 C47.990492,50.7269823 47.870492,50.7475379 47.7927142,50.7297601 C47.7321586,50.7164268 47.7349364,50.6925379 47.705492,50.6514268 C47.695492,50.6369823 47.6743808,50.6353156 47.6588253,50.6292045 C47.630492,50.618649 47.6282697,50.5942045 47.6216031,50.5692045 C47.5960475,50.4764268 47.4110475,50.5914268 47.3827142,50.4614268 C47.3766031,50.4314268 47.3866031,50.3803156 47.3438253,50.373649 C47.2943808,50.3658712 47.2927142,50.318649 47.2927142,50.2792045 C47.2921586,50.2469823 47.295492,50.2019823 47.2610475,50.183649 C47.2171586,50.1603156 47.2066031,50.1708712 47.1932697,50.1214268 C47.1766031,50.0580934 47.1332697,50.1242045 47.0977142,50.1103156 C47.0216031,50.0814268 47.0349364,50.118649 46.9738253,50.1508712 C46.8688253,50.2058712 46.8577142,49.9480934 46.820492,49.8975379 C46.750492,49.8014268 46.7693808,50.0169823 46.7216031,50.043649 C46.6771586,50.0675379 46.6321586,50.0119823 46.6166031,49.978649 C46.6071586,49.958649 46.6010475,49.9375379 46.5893808,49.9180934 C46.5721586,49.8903156 46.5399364,49.878649 46.5221586,49.8508712 C46.5066031,49.8258712 46.4849364,49.7969823 46.4743808,49.7703156 C46.4649364,49.7469823 46.4666031,49.7169823 46.4488253,49.6975379 C46.4266031,49.673649 46.4532697,49.6325379 46.4699364,49.6014268 C46.4977142,49.5914268 46.5388253,49.6125379 46.5588253,49.6297601 C46.6088253,49.6725379 46.6843808,49.8547601 46.7710475,49.8214268 C46.7532697,49.7975379 46.7643808,49.768649 46.7516031,49.7430934 C46.7388253,49.7164268 46.7116031,49.7003156 46.6921586,49.6780934 C46.6466031,49.6269823 46.5977142,49.5764268 46.5682697,49.5142045 C46.5427142,49.4597601 46.5310475,49.403649 46.4788253,49.3664268 C46.4349364,49.3342045 46.3449364,49.3042045 46.3688253,49.2342045 C46.4049364,49.2408712 46.4299364,49.2692045 46.4560475,49.2919823 C46.4932697,49.323649 46.540492,49.3403156 46.5843808,49.3614268 C46.6643808,49.4003156 46.7560475,49.4275379 46.8243808,49.4858712 C46.8660475,49.5219823 46.8443808,49.5997601 46.8971586,49.6442045 C46.9360475,49.6764268 46.995492,49.7847601 47.0666031,49.7419823 C47.0938253,49.7253156 47.1049364,49.6942045 47.1310475,49.6764268 C47.1599364,49.6564268 47.2082697,49.6375379 47.240492,49.6225379 C47.2610475,49.613649 47.2943808,49.6158712 47.3110475,49.5997601 C47.3360475,49.5758712 47.275492,49.5053156 47.2616031,49.4869823 C47.2077142,49.4175379 47.1582697,49.3419823 47.0860475,49.2892045 C47.0488253,49.2619823 47.0127142,49.2330934 46.9699364,49.2153156 C46.945492,49.2058712 46.9049364,49.2069823 46.8999364,49.1742045 C46.9116031,49.1819823 46.9188253,49.1797601 46.9210475,49.1680934 C46.9199364,49.1464268 46.8888253,49.1458712 46.8738253,49.1414268 C46.8382697,49.1314268 46.8138253,49.1314268 46.7993808,49.1019823 C46.7816031,49.0647601 46.7177142,49.0664268 46.6838253,49.0575379 C46.6327142,49.0447601 46.5927142,49.0064268 46.5443808,48.9875379 C46.4866031,48.9653156 46.4432697,48.988649 46.3877142,49.0008712 C46.3310475,49.0130934 46.1093808,49.553649 46.0532697,49.5675379 C45.9982697,49.5814268 45.9193808,49.5358712 45.9116031,49.4530934 C45.9371586,49.4880934 45.7666031,49.263649 45.7738253,49.2503156 C45.4832697,49.7653156 45.785492,49.4464268 45.7871586,49.4192045 C45.7882697,49.3897601 45.880492,49.2375379 46.0571586,48.8608712 C46.0943808,48.7797601 46.0116031,49.6203156 45.9282697,49.6592045 C45.8593808,49.6914268 45.7871586,49.6364268 45.7066031,49.6203156 C45.6610475,49.6114268 45.6943808,49.2264268 45.6438253,49.2580934 C45.315492,49.4669823 45.6538253,49.7625379 45.5893808,49.7697601 C45.4888253,49.7803156 45.1749364,49.8158712 45.0710475,49.8030934 C45.0243808,49.7975379 45.0349364,49.6919823 44.8871586,49.4608712 C44.8682697,49.4314268 44.7827142,49.1308712 44.765492,49.1625379 C44.7427142,49.2025379 44.6332697,49.5842045 44.6243808,49.5475379 C44.5832697,49.5797601 44.6849364,48.9958712 44.5910475,49.1125379 C44.5688253,49.1403156 44.2343808,49.5142045 44.2010475,49.5169823 C44.1766031,49.5197601 44.4682697,49.1347601 44.4482697,49.1419823 C44.3988253,49.1592045 44.0688253,49.6108712 44.0277142,49.6403156 C43.9721586,49.6803156 44.0288253,49.4903156 43.9616031,49.543649 C43.9016031,49.5919823 44.0810475,49.3692045 44.0249364,49.4214268 C43.9860475,49.4569823 43.9099364,49.4997601 43.9038253,49.5569823 C43.8977142,49.6214268 43.930492,49.6658712 43.8827142,49.7258712 C43.8638253,49.7492045 43.8416031,49.7697601 43.8243808,49.7942045 C43.810492,49.8142045 43.8071586,49.8475379 43.7832697,49.8597601 C43.7182697,49.893649 43.7349364,49.913649 43.7543808,49.9842045 C43.7710475,50.043649 43.7649364,50.1553156 43.7510475,50.2164268 C43.7410475,50.2630934 43.6938253,50.3764268 43.6366031,50.3492045 C43.6066031,50.3353156 43.5749364,50.323649 43.5471586,50.3503156 C43.5349364,50.3625379 43.5266031,50.3769823 43.5227142,50.393649 C43.5032697,50.3942045 43.4843808,50.3964268 43.465492,50.4008712 C43.4299364,50.4080934 43.3910475,50.4169823 43.3566031,50.4019823 C43.3216031,50.3869823 43.275492,50.3580934 43.2360475,50.3708712 C43.2027142,50.3819823 43.1377142,50.4103156 43.1238253,50.4447601 C43.1182697,50.4603156 43.1382697,50.4997601 43.1377142,50.5197601 C43.1371586,50.5564268 43.1660475,50.6108712 43.1543808,50.6447601 C43.1271586,50.6308712 43.0882697,50.6264268 43.0699364,50.6008712 C43.0527142,50.5775379 43.0277142,50.5825379 43.0088253,50.5592045 C43.0043808,50.6003156 42.9893808,50.6564268 42.9416031,50.6675379 C42.8938253,50.678649 42.8482697,50.6408712 42.800492,50.6530934 C42.6760475,50.6858712 42.8760475,50.8419823 42.9027142,50.8725379 C42.9471586,50.9225379 42.9627142,50.988649 42.995492,51.0458712 C43.030492,51.1075379 43.105492,51.128649 43.1488253,51.1830934 C43.1849364,51.228649 43.1927142,51.2914268 43.2438253,51.3253156 C43.3016031,51.3642045 43.3532697,51.4003156 43.3771586,51.4675379 C43.4016031,51.443649 43.4521586,51.5547601 43.5038253,51.4658712 C43.5321586,51.4175379 43.5799364,51.3758712 43.6110475,51.4547601 C43.6371586,51.5214268 43.6116031,51.5653156 43.6688253,51.623649 C43.7143808,51.6692045 43.7116031,51.723649 43.635492,51.7192045 C43.6482697,51.753649 43.6688253,51.7869823 43.6360475,51.8180934 C43.620492,51.8330934 43.5782697,51.8642045 43.6093808,51.8858712 C43.6438253,51.8697601 43.6816031,51.8603156 43.7160475,51.8447601 C43.7538253,51.8280934 43.7916031,51.7892045 43.835492,51.7903156 C43.8388253,51.8042045 43.7771586,51.8458712 43.8149364,51.848649 C43.8466031,51.8514268 43.8916031,51.8180934 43.9177142,51.8458712 C43.9466031,51.8764268 43.910492,51.9219823 43.9271586,51.9558712 C43.9438253,51.9897601 44.0010475,51.9642045 44.0293808,51.9692045 C44.0166031,51.9992045 43.9727142,51.993649 43.9488253,52.0053156 C44.0049364,52.0742045 43.9316031,52.1753156 43.8499364,52.1764268 C43.810492,52.1769823 43.6710475,52.0180934 43.665492,52.1203156 C43.6638253,52.1508712 43.6732697,52.1875379 43.6832697,52.2164268 C43.6960475,52.253649 43.7877142,52.2380934 43.8199364,52.2519823 C43.8671586,52.2714268 43.9232697,52.3169823 43.9416031,52.3647601 C43.960492,52.4142045 44.0016031,52.4464268 44.0177142,52.4942045 C44.0471586,52.5814268 44.1316031,52.5930934 44.2188253,52.618649 C44.3349364,52.6530934 44.2777142,52.8325379 44.2716031,52.9180934 C44.2660475,53.0025379 44.385492,53.0225379 44.4399364,53.0703156 C44.4999364,53.1225379 44.5099364,53.2247601 44.4093808,53.2330934 C44.3582697,53.2375379 44.2738253,53.2142045 44.2549364,53.2792045 C44.2277142,53.3719823 44.3677142,53.3614268 44.4282697,53.3825379 C44.4571586,53.3919823 44.5727142,53.4042045 44.5832697,53.4308712 C44.5993808,53.4719823 44.5860475,53.528649 44.6010475,53.573649 C44.6371586,53.6847601 44.7421586,53.7603156 44.8421586,53.8158712 C45.0582697,53.9364268 45.3149364,54.0130934 45.5527142,54.0808712 C45.6849364,54.118649 45.8199364,54.1480934 45.9560475,54.1669823 C46.0866031,54.1847601 46.2032697,54.173649 46.3149364,54.2469823 C46.3921586,54.2969823 46.4416031,54.2597601 46.5210475,54.2730934 C46.5549364,54.2792045 46.5699364,54.3103156 46.5949364,54.3292045 C46.6238253,54.3508712 46.6566031,54.3225379 46.6877142,54.3342045 C46.6921586,54.3142045 46.6899364,54.2942045 46.6810475,54.2753156 C46.7410475,54.2975379 46.815492,54.3619823 46.8788253,54.3103156 C46.910492,54.2847601 46.9321586,54.2492045 46.9649364,54.2247601 C47.0038253,54.2275379 47.0427142,54.2297601 47.0816031,54.2303156 C47.2466031,54.2308712 47.3760475,54.1553156 47.5071586,54.0675379 C47.6477142,53.973649 47.8177142,53.9714268 47.9810475,53.9558712 C48.1538253,53.9392045 48.3377142,53.9169823 48.4949364,53.8414268 C48.6332697,53.7753156 48.6682697,53.6542045 48.7071586,53.5203156 C48.7493808,53.3747601 48.8771586,53.3158712 48.9610475,53.198649 Z M51.5738253,52.108649 C51.6182697,52.1203156 51.6893808,52.0925379 51.7171586,52.0553156 C51.7549364,52.0042045 51.7232697,51.9369823 51.6577142,51.9753156 C51.6332697,51.9897601 51.6360475,52.0169823 51.6177142,52.0342045 C51.5966031,52.0525379 51.5943808,52.0308712 51.5749364,52.0264268 C51.545492,52.0197601 51.4993808,52.0580934 51.4899364,52.083649 C51.450492,52.0825379 51.415492,52.1264268 51.4338253,52.1614268 C51.485492,52.1419823 51.5171586,52.0930934 51.5738253,52.108649 Z"/>
     </g>
+    <g id="status-available-shape" fill="none"><path d="M6.751 1c-3.176 0-5.751 2.267-5.751 5.063 0 1.392.639 2.653 1.671 3.568-.179.633-.534 1.493-1.236 2.332.12.213 2.096-.537 3.489-1.098.575.169 1.189.261 1.828.261 3.177 0 5.752-2.267 5.752-5.063s-2.576-5.063-5.752-5.063z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fff"/><path d="M6.751 1c-3.176 0-5.751 2.267-5.751 5.063 0 1.392.639 2.653 1.671 3.568-.179.633-.534 1.493-1.236 2.332.12.213 2.096-.537 3.489-1.098.575.169 1.189.261 1.828.261 3.177 0 5.752-2.267 5.752-5.063s-2.576-5.063-5.752-5.063zm1.721 3.036c.42 0 .76.342.76.762 0 .422-.34.763-.76.763s-.761-.341-.761-.763c0-.42.341-.762.761-.762zm-3.47 0c.42 0 .761.342.761.762 0 .422-.341.763-.761.763s-.761-.341-.761-.763c0-.42.341-.762.761-.762zm1.758 5.551l-.023-.001-.023.001c-1.536 0-3.252-.999-3.647-2.681 1.037.481 2.487.691 3.67.691 1.183 0 2.633-.21 3.67-.691-.394 1.682-2.111 2.681-3.647 2.681z" fill="#6DB23D"/></g>
+    <g id="status-unavailable-shape" fill="none"><path d="M6.751 1c-3.176 0-5.751 2.267-5.751 5.063 0 1.392.639 2.653 1.671 3.568-.179.633-.534 1.493-1.236 2.332.12.213 2.096-.537 3.489-1.098.575.169 1.189.261 1.828.261 3.177 0 5.752-2.267 5.752-5.063s-2.576-5.063-5.752-5.063z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fff"/><path d="M6.751 1c-3.176 0-5.751 2.267-5.751 5.063 0 1.392.639 2.653 1.671 3.568-.179.633-.534 1.493-1.236 2.332.12.213 2.096-.537 3.489-1.098.575.169 1.189.261 1.828.261 3.177 0 5.752-2.267 5.752-5.063s-2.576-5.063-5.752-5.063z" fill="#DB3F33"/><path fill="#fff" d="M4 5h6v2h-6z"/></g>
   </defs>
   <use id="add" xlink:href="#add-shape"/>
   <use id="add-hover" xlink:href="#add-shape"/>
   <use id="add-active" xlink:href="#add-shape"/>
   <use id="audio" xlink:href="#audio-shape"/>
   <use id="audio-hover" xlink:href="#audio-shape"/>
   <use id="audio-active" xlink:href="#audio-shape"/>
   <use id="block" xlink:href="#block-shape"/>
@@ -128,9 +130,11 @@
   <use id="video" xlink:href="#video-shape"/>
   <use id="video-hover" xlink:href="#video-shape"/>
   <use id="video-active" xlink:href="#video-shape"/>
   <use id="tour" xlink:href="#tour-shape"/>
   <use id="screen-white" xlink:href="#screen-shape"/>
   <use id="screen-disabled" xlink:href="#screen-shape"/>
   <use id="screenmute-white" xlink:href="#screenmute-shape"/>
   <use id="delete" xlink:href="#delete-shape"/>
+  <use id="status-available" xlink:href="#status-available-shape"/>
+  <use id="status-unavailable" xlink:href="#status-unavailable-shape"/>
 </svg>
deleted file mode 100644
--- a/browser/components/loop/content/shared/img/svg/glyph-settings-16x16.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
-  <path fill-rule="evenodd" fill="#131311" d="M14.77,8c0,0.804,0.262,1.548,0.634,1.678L16,9.887 c-0.205,0.874-0.553,1.692-1.011,2.434l-0.567-0.272c-0.355-0.171-1.066,0.17-1.635,0.738c-0.569,0.569-0.909,1.279-0.738,1.635 l0.273,0.568c-0.741,0.46-1.566,0.79-2.438,0.998l-0.205-0.584c-0.13-0.372-0.874-0.634-1.678-0.634s-1.548,0.262-1.678,0.634 l-0.209,0.596c-0.874-0.205-1.692-0.553-2.434-1.011l0.272-0.567c0.171-0.355-0.17-1.066-0.739-1.635 c-0.568-0.568-1.279-0.909-1.635-0.738l-0.568,0.273c-0.46-0.741-0.79-1.566-0.998-2.439l0.584-0.205 C0.969,9.547,1.231,8.804,1.231,8c0-0.804-0.262-1.548-0.634-1.678L0,6.112c0.206-0.874,0.565-1.685,1.025-2.427l0.554,0.266 c0.355,0.171,1.066-0.17,1.635-0.738c0.569-0.568,0.909-1.28,0.739-1.635L3.686,1.025c0.742-0.46,1.553-0.818,2.427-1.024 l0.209,0.596C6.453,0.969,7.197,1.23,8.001,1.23s1.548-0.262,1.678-0.634l0.209-0.596c0.874,0.205,1.692,0.553,2.434,1.011 l-0.272,0.567c-0.171,0.355,0.17,1.066,0.738,1.635c0.569,0.568,1.279,0.909,1.635,0.738l0.568-0.273 c0.46,0.741,0.79,1.566,0.998,2.438l-0.584,0.205C15.032,6.452,14.77,7.196,14.77,8z M8.001,3.661C5.604,3.661,3.661,5.603,3.661,8 c0,2.397,1.943,4.34,4.339,4.34c2.397,0,4.339-1.943,4.339-4.34C12.34,5.603,10.397,3.661,8.001,3.661z"/>
-</svg>
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -441,17 +441,21 @@ var inChrome = typeof Components != "und
       return;
     }
     mozLoop.telemetryAddValue("LOOP_SHARING_ROOM_URL", bucket);
   }
 
   // We can alias `subarray` to `slice` when the latter is not available, because
   // they're semantically identical.
   if (!Uint8Array.prototype.slice) {
+    /* eslint-disable */
+    // Eslint disabled for no-extend-native; Specific override needed for Firefox 37
+    // and earlier, also for other browsers.
     Uint8Array.prototype.slice = Uint8Array.prototype.subarray;
+    /* eslint-enable */
   }
 
   /**
    * Binary-compatible Base64 decoding.
    *
    * Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
    *
    * @param {String} base64str The string to decode.
@@ -731,16 +735,44 @@ var inChrome = typeof Components != "und
       // If the value of the object property evaluates to |false|, delete it.
       if (!obj[prop]) {
         delete obj[prop];
       }
     }
     return obj;
   }
 
+  /**
+   * Truncate a string if it exceeds the length as defined in `maxLen`, which
+   * is defined as '72' characters by default. If the string needs trimming,
+   * it'll be suffixed with the unicode ellipsis char, \u2026.
+   *
+   * @param  {String} str    The string to truncate, if needed.
+   * @param  {Number} maxLen Maximum number of characters that the string is
+   *                         allowed to contain. Optional, defaults to 72.
+   * @return {String} Truncated version of `str`.
+   */
+  function truncate(str, maxLen) {
+    maxLen = maxLen || 72;
+
+    if (str.length > maxLen) {
+      var substring = str.substr(0, maxLen);
+      // XXX Due to the fact that we have two different l10n libraries.
+      var direction = mozL10n.getDirection ? mozL10n.getDirection() :
+                      mozL10n.language.direction;
+      if (direction === "rtl") {
+        return "…" + substring;
+      }
+
+      return substring + "…";
+    }
+
+    return str;
+  }
+
   this.utils = {
     CALL_TYPES: CALL_TYPES,
     FAILURE_DETAILS: FAILURE_DETAILS,
     REST_ERRNOS: REST_ERRNOS,
     WEBSOCKET_REASONS: WEBSOCKET_REASONS,
     STREAM_PROPERTIES: STREAM_PROPERTIES,
     SCREEN_SHARE_STATES: SCREEN_SHARE_STATES,
     ROOM_INFO_FAILURES: ROOM_INFO_FAILURES,
@@ -759,11 +791,12 @@ var inChrome = typeof Components != "und
     getUnsupportedPlatform: getUnsupportedPlatform,
     hasAudioOrVideoDevices: hasAudioOrVideoDevices,
     locationData: locationData,
     atob: atob,
     btoa: btoa,
     strToUint8Array: strToUint8Array,
     Uint8ArrayToStr: Uint8ArrayToStr,
     objectDiff: objectDiff,
-    stripFalsyValues: stripFalsyValues
+    stripFalsyValues: stripFalsyValues,
+    truncate: truncate
   };
 }).call(inChrome ? this : loop.shared);
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -49,17 +49,16 @@ browser.jar:
   content/browser/loop/shared/img/hangup-inverse-14x14.png      (content/shared/img/hangup-inverse-14x14.png)
   content/browser/loop/shared/img/hangup-inverse-14x14@2x.png   (content/shared/img/hangup-inverse-14x14@2x.png)
   content/browser/loop/shared/img/mute-inverse-14x14.png        (content/shared/img/mute-inverse-14x14.png)
   content/browser/loop/shared/img/mute-inverse-14x14@2x.png     (content/shared/img/mute-inverse-14x14@2x.png)
   content/browser/loop/shared/img/video-inverse-14x14.png       (content/shared/img/video-inverse-14x14.png)
   content/browser/loop/shared/img/video-inverse-14x14@2x.png    (content/shared/img/video-inverse-14x14@2x.png)
   content/browser/loop/shared/img/dropdown-inverse.png          (content/shared/img/dropdown-inverse.png)
   content/browser/loop/shared/img/dropdown-inverse@2x.png       (content/shared/img/dropdown-inverse@2x.png)
-  content/browser/loop/shared/img/svg/glyph-settings-16x16.svg  (content/shared/img/svg/glyph-settings-16x16.svg)
   content/browser/loop/shared/img/svg/glyph-account-16x16.svg   (content/shared/img/svg/glyph-account-16x16.svg)
   content/browser/loop/shared/img/svg/glyph-signin-16x16.svg    (content/shared/img/svg/glyph-signin-16x16.svg)
   content/browser/loop/shared/img/svg/glyph-signout-16x16.svg   (content/shared/img/svg/glyph-signout-16x16.svg)
   content/browser/loop/shared/img/svg/glyph-help-16x16.svg      (content/shared/img/svg/glyph-help-16x16.svg)
   content/browser/loop/shared/img/audio-call-avatar.svg         (content/shared/img/audio-call-avatar.svg)
   content/browser/loop/shared/img/beta-ribbon.svg               (content/shared/img/beta-ribbon.svg)
   content/browser/loop/shared/img/check.svg                     (content/shared/img/check.svg)
   content/browser/loop/shared/img/icons-10x10.svg               (content/shared/img/icons-10x10.svg)
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -70,9 +70,11 @@ config:
 	@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.rooms = true;" >> content/config.js
 	@echo "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';" >> content/config.js
 	@echo "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';" >> content/config.js
 	@echo "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';" >> content/config.js
 	@echo "loop.config.generalSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';" >> content/config.js
+	@echo "loop.config.tilesIframeUrl = 'https://tiles.cdn.mozilla.net/iframe.html';" >> content/config.js
+	@echo "loop.config.tilesSupportUrl = 'https://support.mozilla.org/tiles-firefox-hello';" >> content/config.js
 	@echo "loop.config.unsupportedPlatformUrl = 'https://support.mozilla.org/en-US/kb/which-browsers-will-work-firefox-hello-video-chat';" >> content/config.js
--- a/browser/components/loop/standalone/content/css/webapp.css
+++ b/browser/components/loop/standalone/content/css/webapp.css
@@ -46,16 +46,47 @@ body,
   background-repeat: no-repeat;
 }
 
 .header-box {
   padding: 1rem 5rem;
   margin-top: 2rem;
 }
 
+/* Waiting info offer */
+
+.standalone .empty-room-message {
+  font-size: 1.2em;
+  font-weight: bold;
+}
+
+.standalone .room-waiting-area {
+  display: flex;
+  justify-content: space-between;
+  margin: 3em auto 1em;
+}
+
+.standalone .room-waiting-help {
+  background: transparent url("../shared/img/svg/glyph-help-16x16.svg") no-repeat;
+  display: inline-block;
+  height: 16px;
+  margin-left: 5px;
+  vertical-align: middle;
+  width: 16px;
+}
+
+.standalone .room-waiting-tile {
+  border: 0;
+  border-radius: 5px;
+  /* These sizes are the size of the tile image and title */
+  height: 204px;
+  /* Override the default iframe 300px width with the inherited width */
+  width: 100%;
+}
+
 /*
  * Top/Bottom spacing
  **/
 .container {
   display: flex;
   flex-direction: column;
   margin: 0 auto;
 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -146,17 +146,27 @@ loop.standaloneRoomViews = (function(moz
         }
         case ROOM_STATES.JOINING:
         case ROOM_STATES.JOINED:
         case ROOM_STATES.SESSION_CONNECTED: {
           return (
             React.createElement("div", {className: "room-inner-info-area"}, 
               React.createElement("p", {className: "empty-room-message"}, 
                 mozL10n.get("rooms_only_occupant_label")
-              )
+              ), 
+              React.createElement("p", {className: "room-waiting-area"}, 
+                mozL10n.get("rooms_read_while_wait_offer"), 
+                React.createElement("a", {href: loop.config.tilesSupportUrl, 
+                  onClick: this.recordClick, 
+                  rel: "noreferrer", 
+                  target: "_blank"}, 
+                  React.createElement("i", {className: "room-waiting-help"})
+                )
+              ), 
+              React.createElement("iframe", {className: "room-waiting-tile", src: loop.config.tilesIframeUrl})
             )
           );
         }
         case ROOM_STATES.FULL: {
           return (
             React.createElement("div", {className: "room-inner-info-area"}, 
               React.createElement("p", {className: "full-room-message"}, 
                 mozL10n.get("rooms_room_full_label")
@@ -456,40 +466,41 @@ loop.standaloneRoomViews = (function(moz
     render: function() {
       var displayScreenShare = !!(this.state.receivingScreenShare ||
         this.props.screenSharePosterUrl);
 
       return (
         React.createElement("div", {className: "room-conversation-wrapper standalone-room-wrapper"}, 
           React.createElement("div", {className: "beta-logo"}), 
           React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}), 
-          React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore, 
-                                  dispatcher: this.props.dispatcher, 
-                                  failureReason: this.state.failureReason, 
-                                  isFirefox: this.props.isFirefox, 
-                                  joinRoom: this.joinRoom, 
-                                  roomState: this.state.roomState, 
-                                  roomUsed: this.state.used}), 
           React.createElement(sharedViews.MediaLayoutView, {
             dispatcher: this.props.dispatcher, 
             displayScreenShare: displayScreenShare, 
             isLocalLoading: this._isLocalLoading(), 
             isRemoteLoading: this._isRemoteLoading(), 
             isScreenShareLoading: this._isScreenShareLoading(), 
             localPosterUrl: this.props.localPosterUrl, 
             localSrcVideoObject: this.state.localSrcVideoObject, 
             localVideoMuted: this.state.videoMuted, 
             matchMedia: this.state.matchMedia || window.matchMedia.bind(window), 
             remotePosterUrl: this.props.remotePosterUrl, 
             remoteSrcVideoObject: this.state.remoteSrcVideoObject, 
             renderRemoteVideo: this.shouldRenderRemoteVideo(), 
             screenSharePosterUrl: this.props.screenSharePosterUrl, 
             screenShareVideoObject: this.state.screenShareVideoObject, 
             showContextRoomName: true, 
-            useDesktopPaths: false}), 
+            useDesktopPaths: false}, 
+            React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore, 
+              dispatcher: this.props.dispatcher, 
+              failureReason: this.state.failureReason, 
+              isFirefox: this.props.isFirefox, 
+              joinRoom: this.joinRoom, 
+              roomState: this.state.roomState, 
+              roomUsed: this.state.used})
+          ), 
           React.createElement(sharedViews.ConversationToolbar, {
             audio: {enabled: !this.state.audioMuted,
                     visible: this._roomIsActive()}, 
             dispatcher: this.props.dispatcher, 
             edit: { visible: false, enabled: false}, 
             enableHangup: this._roomIsActive(), 
             hangup: this.leaveRoom, 
             hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -147,16 +147,26 @@ loop.standaloneRoomViews = (function(moz
         case ROOM_STATES.JOINING:
         case ROOM_STATES.JOINED:
         case ROOM_STATES.SESSION_CONNECTED: {
           return (
             <div className="room-inner-info-area">
               <p className="empty-room-message">
                 {mozL10n.get("rooms_only_occupant_label")}
               </p>
+              <p className="room-waiting-area">
+                {mozL10n.get("rooms_read_while_wait_offer")}
+                <a href={loop.config.tilesSupportUrl}
+                  onClick={this.recordClick}
+                  rel="noreferrer"
+                  target="_blank">
+                  <i className="room-waiting-help"></i>
+                </a>
+              </p>
+              <iframe className="room-waiting-tile" src={loop.config.tilesIframeUrl} />
             </div>
           );
         }
         case ROOM_STATES.FULL: {
           return (
             <div className="room-inner-info-area">
               <p className="full-room-message">
                 {mozL10n.get("rooms_room_full_label")}
@@ -456,40 +466,41 @@ loop.standaloneRoomViews = (function(moz
     render: function() {
       var displayScreenShare = !!(this.state.receivingScreenShare ||
         this.props.screenSharePosterUrl);
 
       return (
         <div className="room-conversation-wrapper standalone-room-wrapper">
           <div className="beta-logo" />
           <StandaloneRoomHeader dispatcher={this.props.dispatcher} />
-          <StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}
-                                  dispatcher={this.props.dispatcher}
-                                  failureReason={this.state.failureReason}
-                                  isFirefox={this.props.isFirefox}
-                                  joinRoom={this.joinRoom}
-                                  roomState={this.state.roomState}
-                                  roomUsed={this.state.used} />
           <sharedViews.MediaLayoutView
             dispatcher={this.props.dispatcher}
             displayScreenShare={displayScreenShare}
             isLocalLoading={this._isLocalLoading()}
             isRemoteLoading={this._isRemoteLoading()}
             isScreenShareLoading={this._isScreenShareLoading()}
             localPosterUrl={this.props.localPosterUrl}
             localSrcVideoObject={this.state.localSrcVideoObject}
             localVideoMuted={this.state.videoMuted}
             matchMedia={this.state.matchMedia || window.matchMedia.bind(window)}
             remotePosterUrl={this.props.remotePosterUrl}
             remoteSrcVideoObject={this.state.remoteSrcVideoObject}
             renderRemoteVideo={this.shouldRenderRemoteVideo()}
             screenSharePosterUrl={this.props.screenSharePosterUrl}
             screenShareVideoObject={this.state.screenShareVideoObject}
             showContextRoomName={true}
-            useDesktopPaths={false} />
+            useDesktopPaths={false}>
+            <StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}
+              dispatcher={this.props.dispatcher}
+              failureReason={this.state.failureReason}
+              isFirefox={this.props.isFirefox}
+              joinRoom={this.joinRoom}
+              roomState={this.state.roomState}
+              roomUsed={this.state.used} />
+          </sharedViews.MediaLayoutView>
           <sharedViews.ConversationToolbar
             audio={{enabled: !this.state.audioMuted,
                     visible: this._roomIsActive()}}
             dispatcher={this.props.dispatcher}
             edit={{ visible: false, enabled: false }}
             enableHangup={this._roomIsActive()}
             hangup={this.leaveRoom}
             hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
--- a/browser/components/loop/standalone/content/l10n/en-US/loop.properties
+++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
@@ -106,16 +106,21 @@ rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
 rooms_room_joined_label=Someone has joined the conversation!
 rooms_room_join_label=Join the conversation
 rooms_display_name_guest=Guest
 rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
 rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.
 room_information_failure_not_available=No information about this conversation is available. Please request a new link from the person who sent it to you.
 room_information_failure_unsupported_browser=Your browser cannot access any information about this conversation. Please make sure you're using the latest version.
 
+## LOCALIZATION_NOTE(rooms_read_while_wait_offer): This string is followed by a
+# tile/offer image and title that are provided by a separate service that has
+# localized content.
+rooms_read_while_wait_offer=Want something to read while you wait?
+
 ## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
 ## replaced by the brand name and {{currentStatus}} will be replaced
 ## by the current call status (Connecting, Ringing, etc.)
 standalone_title_with_status={{clientShortname}} — {{currentStatus}}
 ## LOCALIZATION_NOTE(standalone_title_with_room_name): {{roomName}} will be replaced
 ## by the name of the conversation and {{clientShortname}} will be
 ## replaced by the brand name.
 standalone_title_with_room_name={{roomName}} — {{clientShortname}}
--- a/browser/components/loop/standalone/package.json
+++ b/browser/components/loop/standalone/package.json
@@ -7,18 +7,18 @@
     "url": "git@github.com:mozilla/loop-client.git"
   },
   "engines": {
     "node": "0.10.x",
     "npm": "1.3.x"
   },
   "dependencies": {},
   "devDependencies": {
-    "eslint": "0.24.x",
-    "eslint-plugin-react": "2.7.x",
+    "eslint": "1.0.x",
+    "eslint-plugin-react": "3.2.x",
     "express": "4.x"
   },
   "scripts": {
     "test": "make test",
     "start": "make runserver"
   },
   "license": "MPL-2.0"
 }
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -7,16 +7,18 @@
 /* XXX We should enable these and fix the warnings, but at the time of this
  * writing, we're just bootstrapping the linting infrastructure.
  */
 /* eslint-disable no-path-concat,no-process-exit */
 
 var express = require("express");
 var app = express();
 
+var path = require("path");
+
 var port = process.env.PORT || 3000;
 var feedbackApiUrl = process.env.LOOP_FEEDBACK_API_URL ||
                      "https://input.allizom.org/api/v1/feedback";
 var feedbackProductName = process.env.LOOP_FEEDBACK_PRODUCT_NAME || "Loop";
 var loopServerUrl = process.env.LOOP_SERVER_URL || "http://localhost:5000";
 
 // Remove trailing slashes as double slashes in the url can confuse the server
 // responses.
@@ -41,56 +43,61 @@ function getConfigFile(req, res) {
     "loop.config.privacyWebsiteUrl = 'https://www.mozilla.org/privacy/firefox-hello/';",
     "loop.config.learnMoreUrl = 'https://www.mozilla.org/hello/';",
     "loop.config.legalWebsiteUrl = 'https://www.mozilla.org/about/legal/terms/firefox-hello/';",
     "loop.config.fxosApp = loop.config.fxosApp || {};",
     "loop.config.fxosApp.name = 'Loop';",
     "loop.config.fxosApp.rooms = true;",
     "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';",
     "loop.config.roomsSupportUrl = 'https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc';",
+    "loop.config.tilesIframeUrl = 'https://tiles.cdn.mozilla.net/iframe.html';",
+    "loop.config.tilesSupportUrl = 'https://support.mozilla.org/tiles-firefox-hello';",
     "loop.config.guestSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';",
     "loop.config.generalSupportUrl = 'https://support.mozilla.org/kb/respond-firefox-hello-invitation-guest-mode';",
     "loop.config.unsupportedPlatformUrl = 'https://support.mozilla.org/en-US/kb/which-browsers-will-work-firefox-hello-video-chat'"
   ].join("\n"));
 }
 
 app.get("/content/config.js", getConfigFile);
 app.get("/content/c/config.js", getConfigFile);
 
 // Various mappings to let us end up with:
 // /test - for the test files
 // /ui - for the ui showcase
 // /content - for the standalone files.
 
-app.use("/ui", express.static(__dirname + "/../ui"));
+app.use("/ui", express.static(path.join(__dirname, "..", "ui")));
+app.use("/ui/loop/", express.static(path.join(__dirname, "..", "content")));
+app.use("/ui/shared/", express.static(path.join(__dirname, "..", "content",
+                                                "shared")));
 
 // This exists exclusively for the unit tests. They are served the
 // whole loop/ directory structure and expect some files in the standalone directory.
-app.use("/standalone/content", express.static(__dirname + "/content"));
+app.use("/standalone/content", express.static(path.join(__dirname, "content")));
 
 // We load /content this from  both /content *and* /../content. The first one
 // does what we need for running in the github loop-client context, the second one
 // handles running in the hg repo under mozilla-central and is used so that the shared
 // files are in the right location.
-app.use("/content", express.static(__dirname + "/content"));
-app.use("/content", express.static(__dirname + "/../content"));
+app.use("/content", express.static(path.join(__dirname, "content")));
+app.use("/content", express.static(path.join(__dirname, "..", "content")));
 // These two are based on the above, but handle call urls, that have a /c/ in them.
-app.use("/content/c", express.static(__dirname + "/content"));
-app.use("/content/c", express.static(__dirname + "/../content"));
+app.use("/content/c", express.static(path.join(__dirname, "content")));
+app.use("/content/c", express.static(path.join(__dirname, "..", "content")));
 
 // Two lines for the same reason as /content above.
-app.use("/test", express.static(__dirname + "/test"));
-app.use("/test", express.static(__dirname + "/../test"));
+app.use("/test", express.static(path.join(__dirname, "test")));
+app.use("/test", express.static(path.join(__dirname, "..", "test")));
 
 // As we don't have hashes on the urls, the best way to serve the index files
 // appears to be to be to closely filter the url and match appropriately.
 function serveIndex(req, res) {
   "use strict";
 
-  return res.sendfile(__dirname + "/content/index.html");
+  return res.sendfile(path.join(__dirname, "content", "index.html"));
 }
 
 app.get(/^\/content\/[\w\-]+$/, serveIndex);
 app.get(/^\/content\/c\/[\w\-]+$/, serveIndex);
 
 var server = app.listen(port);
 
 var baseUrl = "http://localhost:" + port + "/";
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -67,17 +67,18 @@ describe("loop.panel", function() {
         on: sandbox.stub()
       },
       confirm: sandbox.stub(),
       hasEncryptionKey: true,
       logInToFxA: sandbox.stub(),
       logOutFromFxA: sandbox.stub(),
       notifyUITour: sandbox.stub(),
       openURL: sandbox.stub(),
-      getSelectedTabMetadata: sandbox.stub()
+      getSelectedTabMetadata: sandbox.stub(),
+      userProfile: null
     };
 
     document.mozL10n.initialize(navigator.mozLoop);
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     loop.shared.mixins.setRootObject(window);
@@ -131,28 +132,28 @@ describe("loop.panel", function() {
         React.createElement(loop.panel.AvailabilityDropdown));
     });
 
     describe("doNotDisturb preference change", function() {
       beforeEach(function() {
         navigator.mozLoop.doNotDisturb = true;
       });
 
-      it("should toggle the value of mozLoop.doNotDisturb", function() {
+      it("should toggle mozLoop.doNotDisturb to false", function() {
         var availableMenuOption = view.getDOMNode()
-                                    .querySelector(".dnd-make-available");
+                                      .querySelector(".status-available");
 
         TestUtils.Simulate.click(availableMenuOption);
 
         expect(navigator.mozLoop.doNotDisturb).eql(false);
       });
 
       it("should toggle the dropdown menu", function() {
         var availableMenuOption = view.getDOMNode()
-                                    .querySelector(".dnd-status span");
+                                      .querySelector(".dnd-status span");
 
         TestUtils.Simulate.click(availableMenuOption);
 
         expect(view.state.showMenu).eql(true);
       });
     });
   });
 
@@ -230,61 +231,100 @@ describe("loop.panel", function() {
         TestUtils.Simulate.click(
           view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
 
         expect(roomsTab.getDOMNode().classList.contains("selected"))
           .to.be.true;
       });
     });
 
-    describe("AuthLink", function() {
-
+    describe("AccountLink", function() {
       beforeEach(function() {
         navigator.mozLoop.calls = { clearCallInProgress: function() {} };
       });
 
       afterEach(function() {
         delete navigator.mozLoop.logInToFxA;
         delete navigator.mozLoop.calls;
         navigator.mozLoop.fxAEnabled = true;
       });
 
       it("should trigger the FxA sign in/up process when clicking the link",
         function() {
-          navigator.mozLoop.loggedInToFxA = false;
           navigator.mozLoop.logInToFxA = sandbox.stub();
 
           var view = createTestPanelView();
 
           TestUtils.Simulate.click(
-            view.getDOMNode().querySelector(".signin-link a"));
+            view.getDOMNode().querySelector(".signin-link > a"));
 
           sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
         });
 
       it("should close the panel after clicking the link",
         function() {
           navigator.mozLoop.loggedInToFxA = false;
           navigator.mozLoop.logInToFxA = sandbox.stub();
 
           var view = createTestPanelView();
 
           TestUtils.Simulate.click(
-            view.getDOMNode().querySelector(".signin-link a"));
+            view.getDOMNode().querySelector(".signin-link > a"));
 
           sinon.assert.calledOnce(fakeWindow.close);
         });
 
       it("should be hidden if FxA is not enabled",
         function() {
           navigator.mozLoop.fxAEnabled = false;
           var view = TestUtils.renderIntoDocument(
-            React.createElement(loop.panel.AuthLink));
+            React.createElement(loop.panel.AccountLink, {
+              fxAEnabled: false,
+              userProfile: null
+            }));
           expect(view.getDOMNode()).to.be.null;
       });
+
+      it("should add ellipsis to text over 24chars", function() {
+        navigator.mozLoop.userProfile = {
+          email: "reallyreallylongtext@example.com"
+        };
+        var view = createTestPanelView();
+        var node = view.getDOMNode().querySelector(".user-identity");
+
+        expect(node.textContent).to.eql("reallyreallylongtext@exa…");
+      });
+
+      it("should throw an error when user profile is different from {} or null",
+         function() {
+          var warnstub = sandbox.stub(console, "warn");
+          var view = TestUtils.renderIntoDocument(React.createElement(
+            loop.panel.AccountLink, {
+              fxAEnabled: false,
+              userProfile: []
+            }
+          ));
+
+          sinon.assert.calledOnce(warnstub);
+          sinon.assert.calledWithExactly(warnstub, "Warning: Required prop `userProfile` was not correctly specified in `AccountLink`.");
+      });
+
+      it("should throw an error when user profile is different from {} or null",
+         function() {
+          var warnstub = sandbox.stub(console, "warn");
+          var view = TestUtils.renderIntoDocument(React.createElement(
+            loop.panel.AccountLink, {
+              fxAEnabled: false,
+              userProfile: function() {}
+            }
+          ));
+
+          sinon.assert.calledOnce(warnstub);
+          sinon.assert.calledWithExactly(warnstub, "Warning: Required prop `userProfile` was not correctly specified in `AccountLink`.");
+      });
     });
 
     describe("SettingsDropdown", function() {
       function mountTestComponent() {
         return TestUtils.renderIntoDocument(
           React.createElement(loop.panel.SettingsDropdown, {
             mozLoop: fakeMozLoop
           }));
@@ -295,27 +335,49 @@ describe("loop.panel", function() {
         navigator.mozLoop.logOutFromFxA = sandbox.stub();
         navigator.mozLoop.openFxASettings = sandbox.stub();
       });
 
       afterEach(function() {
         navigator.mozLoop.fxAEnabled = true;
       });
 
-      it("should show a signin entry when user is not authenticated",
-        function() {
+      describe("UserLoggedOut", function() {
+        beforeEach(function() {
+          fakeMozLoop.userProfile = null;
+        });
+
+        it("should show a signin entry when user is not authenticated",
+           function() {
+             var view = mountTestComponent();
+
+             expect(view.getDOMNode().querySelectorAll(".icon-signout"))
+               .to.have.length.of(0);
+             expect(view.getDOMNode().querySelectorAll(".icon-signin"))
+               .to.have.length.of(1);
+           });
+
+        it("should hide any account entry when user is not authenticated",
+           function() {
+             var view = mountTestComponent();
+
+             expect(view.getDOMNode().querySelectorAll(".icon-account"))
+               .to.have.length.of(0);
+           });
+
+        it("should sign in the user on click when unauthenticated", function() {
           navigator.mozLoop.loggedInToFxA = false;
-
           var view = mountTestComponent();
 
-          expect(view.getDOMNode().querySelectorAll(".icon-signout"))
-            .to.have.length.of(0);
-          expect(view.getDOMNode().querySelectorAll(".icon-signin"))
-            .to.have.length.of(1);
+          TestUtils.Simulate.click(view.getDOMNode()
+                                     .querySelector(".icon-signin"));
+
+          sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
         });
+      });
 
       it("should show a signout entry when user is authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
 
         var view = mountTestComponent();
 
         expect(view.getDOMNode().querySelectorAll(".icon-signout"))
           .to.have.length.of(1);
@@ -327,53 +389,34 @@ describe("loop.panel", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
 
         var view = mountTestComponent();
 
         expect(view.getDOMNode().querySelectorAll(".icon-account"))
           .to.have.length.of(1);
       });
 
-      it("should open the FxA settings when the account entry is clicked", function() {
-        navigator.mozLoop.userProfile = {email: "test@example.com"};
-
-        var view = mountTestComponent();
+      it("should open the FxA settings when the account entry is clicked",
+         function() {
+           navigator.mozLoop.userProfile = {email: "test@example.com"};
 
-        TestUtils.Simulate.click(
-          view.getDOMNode().querySelector(".icon-account"));
-
-        sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
-      });
-
-      it("should hide any account entry when user is not authenticated",
-        function() {
-          navigator.mozLoop.loggedInToFxA = false;
+           var view = mountTestComponent();
 
-          var view = mountTestComponent();
-
-          expect(view.getDOMNode().querySelectorAll(".icon-account"))
-            .to.have.length.of(0);
-        });
+           TestUtils.Simulate.click(view.getDOMNode()
+                                      .querySelector(".icon-account"));
 
-      it("should sign in the user on click when unauthenticated", function() {
-        navigator.mozLoop.loggedInToFxA = false;
-        var view = mountTestComponent();
-
-        TestUtils.Simulate.click(
-          view.getDOMNode().querySelector(".icon-signin"));
-
-        sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
-      });
+           sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
+         });
 
       it("should sign out the user on click when authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
         var view = mountTestComponent();
 
-        TestUtils.Simulate.click(
-          view.getDOMNode().querySelector(".icon-signout"));
+        TestUtils.Simulate.click(view.getDOMNode()
+                                   .querySelector(".icon-signout"));
 
         sinon.assert.calledOnce(navigator.mozLoop.logOutFromFxA);
       });
     });
 
     describe("Help", function() {
       var view, supportUrl;
 
@@ -719,17 +762,18 @@ describe("loop.panel", function() {
     });
 
     function createTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(loop.panel.RoomList, {
           store: roomStore,
           dispatcher: dispatcher,
           userDisplayName: fakeEmail,
-          mozLoop: fakeMozLoop
+          mozLoop: fakeMozLoop,
+          userProfile: null
         }));
     }
 
     it("should dispatch a GetAllRooms action on mount", function() {
       createTestComponent();
 
       sinon.assert.calledOnce(dispatch);
       sinon.assert.calledWithExactly(dispatch, new sharedActions.GetAllRooms());
--- a/browser/components/loop/test/desktop-local/roomStore_test.js
+++ b/browser/components/loop/test/desktop-local/roomStore_test.js
@@ -147,17 +147,17 @@ describe("loop.store.RoomStore", functio
 
         it("should avoid adding a duplicate room", function() {
           var sampleRoom = fakeRoomList[0];
 
           fakeMozLoop.rooms.trigger("add", "add", sampleRoom);
 
           expect(store.getStoreState().rooms).to.have.length.of(3);
           expect(store.getStoreState().rooms.reduce(function(count, room) {
-            return count += room.roomToken === sampleRoom.roomToken ? 1 : 0;
+            return count + (room.roomToken === sampleRoom.roomToken ? 1 : 0);
           }, 0)).eql(1);
         });
       });
 
       describe("update", function() {
         it("should update a room entry", function() {
           fakeMozLoop.rooms.trigger("update", "update", {
             roomToken: "_nxD4V4FflQ",
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -284,30 +284,34 @@ add_task(function* basicAuthorizationAnd
   info("registering");
   mockPushHandler.registrationPushURL = "https://localhost/pushUrl/guest";
   yield MozLoopService.promiseRegisteredWithServers();
 
   let statusChangedPromise = promiseObserverNotified("loop-status-changed");
   yield loadLoopPanel({stayOnline: true});
   yield statusChangedPromise;
   let loopDoc = document.getElementById("loop-panel-iframe").contentDocument;
-  let visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
-  is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel when not logged in");
+  let accountLogin = loopDoc.getElementsByClassName("signin-link")[0];
+  let visibleEmail = loopDoc.getElementsByClassName("user-identity");
+  is(visibleEmail.length, 0, "No email should be displayed when logged out");
+  is(accountLogin.textContent, "Sign In or Sign Up", "Login/Signup links when logged out");
   is(MozLoopService.userProfile, null, "profile should be null before log-in");
   let loopButton = document.getElementById("loop-button");
   is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
 
   info("Login");
   statusChangedPromise = promiseObserverNotified("loop-status-changed", "login");
   let tokenData = yield MozLoopService.logInToFxA();
   yield statusChangedPromise;
   is(tokenData.access_token, "code1_access_token", "Check access_token");
   is(tokenData.scope, "profile", "Check scope");
   is(tokenData.token_type, "bearer", "Check token_type");
 
+  visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
+
   is(MozLoopService.userProfile.email, "test@example.com", "email should exist in the profile data");
   is(MozLoopService.userProfile.uid, "1234abcd", "uid should exist in the profile data");
   is(visibleEmail.textContent, "test@example.com", "the email should be correct on the panel");
   is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
 
   let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
   is(registrationResponse.response.simplePushURLs.calls, "https://localhost/pushUrl/fxa-calls",
      "Check registered push URL");
@@ -323,17 +327,17 @@ add_task(function* basicAuthorizationAnd
   loopPanel.hidePopup();
 
   info("logout");
   yield MozLoopService.logOutFromFxA();
   checkLoggedOutState();
   registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
   is(registrationResponse.response, null,
      "Check registration was deleted on the server");
-  is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel again after logout");
+  is(accountLogin.textContent, "Sign In or Sign Up", "Login/Signup links when logged out");
   is(MozLoopService.userProfile, null, "userProfile should be null after logout");
 });
 
 add_task(function* loginWithParams401() {
   yield resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
--- a/browser/components/loop/test/shared/utils_test.js
+++ b/browser/components/loop/test/shared/utils_test.js
@@ -660,9 +660,50 @@ describe("loop.shared.utils", function()
         prop2: null,
         prop3: true
       };
 
       sharedUtils.stripFalsyValues(obj);
       expect(obj).to.eql({ prop1: "null", prop3: true });
     });
   });
+
+  describe("#truncate", function() {
+    describe("ltr support", function() {
+      it("should default to 72 chars", function() {
+        var output = sharedUtils.truncate(new Array(75).join());
+
+        expect(output.length).to.eql(73); // 72 + …
+      });
+
+      it("should take a max size argument", function() {
+        var output = sharedUtils.truncate(new Array(73).join(), 20);
+
+        expect(output.length).to.eql(21); // 20 + …
+      });
+    });
+
+    describe("rtl support", function() {
+      var directionStub;
+
+      beforeEach(function() {
+        // XXX should use sandbox
+        // https://github.com/cjohansen/Sinon.JS/issues/781
+        directionStub = sinon.stub(navigator.mozL10n.language, "direction", {
+          get: function() {
+            return "rtl";
+          }
+        });
+      });
+
+      afterEach(function() {
+        directionStub.restore();
+      });
+
+      it("should support RTL", function() {
+        var output = sharedUtils.truncate(new Array(73).join(), 20);
+
+        expect(output.length).to.eql(21); // 20 + …
+        expect(output.substr(0, 1)).to.eql("…");
+      });
+    });
+  });
 });
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -176,16 +176,30 @@ describe("loop.standaloneRoomViews", fun
         it("should display an empty room message on JOINED",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .not.eql(null);
           });
 
+        it("should display a waiting room message and tile iframe on JOINED",
+          function() {
+            var DUMMY_TILE_URL = "http://tile/";
+            loop.config.tilesIframeUrl = DUMMY_TILE_URL;
+            activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
+
+            expect(view.getDOMNode().querySelector(".room-waiting-area"))
+              .not.eql(null);
+
+            var tile = view.getDOMNode().querySelector(".room-waiting-tile");
+            expect(tile).not.eql(null);
+            expect(tile.src).eql(DUMMY_TILE_URL);
+          });
+
         it("should display an empty room message on SESSION_CONNECTED",
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
               .not.eql(null);
           });
 
--- a/browser/components/loop/ui/fake-l10n.js
+++ b/browser/components/loop/ui/fake-l10n.js
@@ -9,17 +9,23 @@
    * /!\ FIXME: THIS IS A HORRID HACK which fakes both the mozL10n and webL10n
    * objects and makes them returning the string id and serialized vars if any,
    * for any requested string id.
    * @type {Object}
    */
   navigator.mozL10n = document.mozL10n = {
     initialize: function(){},
 
-    getDirection: function(){},
+    getDirection: function(){
+      if (document.location.search === "?rtl=1") {
+        return "rtl";
+      }
+
+      return "ltr";
+    },
 
     get: function(stringId, vars) {
 
       // upcase the first letter
       var readableStringId = stringId.replace(/^./, function(match) {
         return match.toUpperCase();
       }).replace(/_/g, " ");  // and convert _ chars to spaces
 
--- a/browser/components/loop/ui/fake-mozLoop.js
+++ b/browser/components/loop/ui/fake-mozLoop.js
@@ -159,11 +159,12 @@ var fakeContacts = [{
     rooms: {
       getAll: function(version, callback) {
         callback(null, [].concat(fakeRooms));
       },
       on: function() {}
     },
     fxAEnabled: true,
     startAlerting: function() {},
-    stopAlerting: function() {}
+    stopAlerting: function() {},
+    userProfile: null
   };
 })();
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -124,16 +124,19 @@ body {
 }
 
 .call-action-group .btn-group-chevron,
 .call-action-group .btn-group {
   /* Prevent box overflow due to long string */
   max-width: 120px;
 }
 
+.room-waiting-tile {
+  background-color: grey;
+}
 /* SVG icons showcase */
 
 .svg-icons h3 {
   clear: left;
 }
 
 .svg-icon-list {
   display: block;
@@ -157,8 +160,14 @@ body {
   margin-left: .5rem;
   border: 0;
 }
 
 /* Temporary until bug 1168829 is completed */
 .standalone.text-chat-example .text-chat-view {
   height: 400px;
 }
+
+/* Force dropdown menus to display. */
+.force-menu-show * {
+  display: inline-block !important;
+}
+
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -10,16 +10,17 @@
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener("DOMContentLoaded", loop.panel.init);
   document.removeEventListener("DOMContentLoaded", loop.conversation.init);
 
   var sharedActions = loop.shared.actions;
 
   // 1. Desktop components
   // 1.1 Panel
+  var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
   var PanelView = loop.panel.PanelView;
   var SignInRequestView = loop.panel.SignInRequestView;
   // 1.2. Conversation Window
   var AcceptCallView = loop.conversationViews.AcceptCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var OngoingConversationView = loop.conversationViews.OngoingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
@@ -433,17 +434,27 @@
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
 
-  var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
+  var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopLoggedIn.userProfile = {
+    email: "text@example.com",
+    uid: "0354b278a381d3cb408bb46ffc01266"
+  };
+
+  var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopLoggedInLongEmail.userProfile = {
+    email: "reallyreallylongtext@example.com",
+    uid: "0354b278a381d3cb408bb46ffc01266"
+  };
 
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
@@ -487,34 +498,36 @@
     propTypes: {
       size: React.PropTypes.string.isRequired
     },
 
     shapes: {
       "10x10": ["close", "close-active", "close-disabled", "dropdown",
         "dropdown-white", "dropdown-active", "dropdown-disabled", "edit",
         "edit-active", "edit-disabled", "edit-white", "expand", "expand-active",
-        "expand-disabled", "minimize", "minimize-active", "minimize-disabled"
+        "expand-disabled", "minimize", "minimize-active", "minimize-disabled",
+        "settings-cog"
       ],
       "14x14": ["audio", "audio-active", "audio-disabled", "facemute",
         "facemute-active", "facemute-disabled", "hangup", "hangup-active",
         "hangup-disabled", "incoming", "incoming-active", "incoming-disabled",
         "link", "link-active", "link-disabled", "mute", "mute-active",
         "mute-disabled", "pause", "pause-active", "pause-disabled", "video",
         "video-white", "video-active", "video-disabled", "volume", "volume-active",
         "volume-disabled"
       ],
       "16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
         "block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
         "contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
         "google-active", "history", "history-hover", "history-active", "leave",
         "precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
         "settings", "settings-hover", "settings-active", "share-darkgrey", "tag",
         "tag-hover", "tag-active", "trash", "unblock", "unblock-hover", "unblock-active",
-        "video", "video-hover", "video-active", "tour"
+        "video", "video-hover", "video-active", "tour", "status-available",
+        "status-unavailable"
       ]
     },
 
     render: function() {
       var icons = this.shapes[this.props.size].map(function(shapeId, i) {
         return (
           React.createElement("li", {className: "svg-icon-entry", key: this.props.size + "-" + i}, 
             React.createElement("p", null, React.createElement(SVGIcon, {shapeId: shapeId, size: this.props.size})), 
@@ -575,29 +588,36 @@
   });
 
   var Example = React.createClass({displayName: "Example",
     propTypes: {
       children: React.PropTypes.oneOfType([
         React.PropTypes.element,
         React.PropTypes.arrayOf(React.PropTypes.element)
       ]).isRequired,
+      cssClass: React.PropTypes.string,
       dashed: React.PropTypes.bool,
       style: React.PropTypes.object,
       summary: React.PropTypes.string.isRequired
     },
 
     makeId: function(prefix) {
       return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
     },
 
     render: function() {
       var cx = React.addons.classSet;
+      var extraCSSClass = {
+        "example": true
+      };
+      if (this.props.cssClass) {
+        extraCSSClass[this.props.cssClass] = true;
+      }
       return (
-        React.createElement("div", {className: "example"}, 
+        React.createElement("div", {className: cx(extraCSSClass)}, 
           React.createElement("h3", {id: this.makeId()}, 
             this.props.summary, 
             React.createElement("a", {href: this.makeId("#")}, " ¶")
           ), 
           React.createElement("div", {className: cx({comp: true, dashed: this.props.dashed}), 
                style: this.props.style}, 
             this.props.children
           )
@@ -688,99 +708,117 @@
     render: function() {
       return (
         React.createElement(ShowCase, null, 
           React.createElement(Section, {name: "PanelView"}, 
             React.createElement("p", {className: "note"}, 
               React.createElement("strong", null, "Note:"), " 332px wide."
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Re-sign-in view"}, 
-              React.createElement(SignInRequestView, {mozLoop: mockMozLoopRooms})
+              React.createElement(SignInRequestView, {mozLoop: mockMozLoopLoggedIn})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Room list tab"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
-                         mozLoop: mockMozLoopRooms, 
+                         mozLoop: mockMozLoopLoggedIn, 
                          notifications: notifications, 
                          roomStore: roomStore, 
-                         selectedTab: "rooms", 
-                         userProfile: {email: "test@example.com"}})
+                         selectedTab: "rooms"})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
-                         mozLoop: mockMozLoopRooms, 
+                         mozLoop: mockMozLoopLoggedIn, 
                          notifications: notifications, 
                          roomStore: roomStore, 
-                         selectedTab: "contacts", 
-                         userProfile: {email: "test@example.com"}})
+                         selectedTab: "contacts"})
+            ), 
+            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab long email"}, 
+              React.createElement(PanelView, {client: mockClient, 
+                         dispatcher: dispatcher, 
+                         mozLoop: mockMozLoopLoggedInLongEmail, 
+                         notifications: notifications, 
+                         roomStore: roomStore, 
+                         selectedTab: "contacts"})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: navigator.mozLoop, 
                          notifications: errNotifications, 
                          roomStore: roomStore})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification - authenticated"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
-                         mozLoop: navigator.mozLoop, 
+                         mozLoop: mockMozLoopLoggedIn, 
                          notifications: errNotifications, 
-                         roomStore: roomStore, 
-                         userProfile: {email: "test@example.com"}})
+                         roomStore: roomStore})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact import success"}, 
               React.createElement(PanelView, {dispatcher: dispatcher, 
-                         mozLoop: mockMozLoopRooms, 
+                         mozLoop: mockMozLoopLoggedIn, 
                          notifications: new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}]), 
                          roomStore: roomStore, 
-                         selectedTab: "contacts", 
-                         userProfile: {email: "test@example.com"}})
+                         selectedTab: "contacts"})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact import error"}, 
               React.createElement(PanelView, {dispatcher: dispatcher, 
-                         mozLoop: mockMozLoopRooms, 
+                         mozLoop: mockMozLoopLoggedIn, 
                          notifications: new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}]), 
                          roomStore: roomStore, 
-                         selectedTab: "contacts", 
-                         userProfile: {email: "test@example.com"}})
+                         selectedTab: "contacts"})
+            )
+          ), 
+
+          React.createElement(Section, {name: "Availability Dropdown"}, 
+            React.createElement("p", {className: "note"}, 
+              React.createElement("strong", null, "Note:"), " 332px wide."
+            ), 
+            React.createElement(Example, {dashed: true, style: {width: "332px", height: "200px"}, 
+                     summary: "AvailabilityDropdown"}, 
+              React.createElement(AvailabilityDropdown, null)
+            ), 
+            React.createElement(Example, {cssClass: "force-menu-show", dashed: true, 
+                     style: {width: "332px", height: "200px"}, 
+                     summary: "AvailabilityDropdown Expanded"}, 
+              React.createElement(AvailabilityDropdown, null)
             )
           ), 
 
           React.createElement(Section, {name: "AcceptCallView"}, 
             React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default / incoming video call"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
-                                mozLoop: mockMozLoopRooms})
+                                mozLoop: mockMozLoopLoggedIn})
               )
             ), 
 
             React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default / incoming audio only call"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_ONLY, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
-                                mozLoop: mockMozLoopRooms})
+                                mozLoop: mockMozLoopLoggedIn})
               )
             )
           ), 
 
           React.createElement(Section, {name: "AcceptCallView-ActiveState"}, 
             React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
-                                mozLoop: mockMozLoopRooms, 
+                                mozLoop: mockMozLoopLoggedIn, 
                                 showMenu: true})
               )
             )
           ), 
 
           React.createElement(Section, {name: "ConversationToolbar"}, 
             React.createElement("h2", null, "Desktop Conversation Window"), 
             React.createElement("div", {className: "fx-embedded override-position"}, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -10,16 +10,17 @@
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener("DOMContentLoaded", loop.panel.init);
   document.removeEventListener("DOMContentLoaded", loop.conversation.init);
 
   var sharedActions = loop.shared.actions;
 
   // 1. Desktop components
   // 1.1 Panel
+  var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
   var PanelView = loop.panel.PanelView;
   var SignInRequestView = loop.panel.SignInRequestView;
   // 1.2. Conversation Window
   var AcceptCallView = loop.conversationViews.AcceptCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var OngoingConversationView = loop.conversationViews.OngoingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
@@ -433,17 +434,27 @@
   loop.store.StoreMixin.register({
     activeRoomStore: activeRoomStore,
     conversationStore: conversationStores[0],
     textChatStore: textChatStore
   });
 
   // Local mocks
 
-  var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
+  var mockMozLoopLoggedIn = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopLoggedIn.userProfile = {
+    email: "text@example.com",
+    uid: "0354b278a381d3cb408bb46ffc01266"
+  };
+
+  var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
+  mockMozLoopLoggedInLongEmail.userProfile = {
+    email: "reallyreallylongtext@example.com",
+    uid: "0354b278a381d3cb408bb46ffc01266"
+  };
 
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
@@ -487,34 +498,36 @@
     propTypes: {
       size: React.PropTypes.string.isRequired
     },
 
     shapes: {
       "10x10": ["close", "close-active", "close-disabled", "dropdown",
         "dropdown-white", "dropdown-active", "dropdown-disabled", "edit",
         "edit-active", "edit-disabled", "edit-white", "expand", "expand-active",
-        "expand-disabled", "minimize", "minimize-active", "minimize-disabled"
+        "expand-disabled", "minimize", "minimize-active", "minimize-disabled",
+        "settings-cog"
       ],
       "14x14": ["audio", "audio-active", "audio-disabled", "facemute",
         "facemute-active", "facemute-disabled", "hangup", "hangup-active",
         "hangup-disabled", "incoming", "incoming-active", "incoming-disabled",
         "link", "link-active", "link-disabled", "mute", "mute-active",
         "mute-disabled", "pause", "pause-active", "pause-disabled", "video",
         "video-white", "video-active", "video-disabled", "volume", "volume-active",
         "volume-disabled"
       ],
       "16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
         "block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
         "contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
         "google-active", "history", "history-hover", "history-active", "leave",
         "precall", "precall-hover", "precall-active", "screen-white", "screenmute-white",
         "settings", "settings-hover", "settings-active", "share-darkgrey", "tag",
         "tag-hover", "tag-active", "trash", "unblock", "unblock-hover", "unblock-active",
-        "video", "video-hover", "video-active", "tour"
+        "video", "video-hover", "video-active", "tour", "status-available",
+        "status-unavailable"
       ]
     },
 
     render: function() {
       var icons = this.shapes[this.props.size].map(function(shapeId, i) {
         return (
           <li className="svg-icon-entry" key={this.props.size + "-" + i}>
             <p><SVGIcon shapeId={shapeId} size={this.props.size} /></p>
@@ -575,29 +588,36 @@
   });
 
   var Example = React.createClass({
     propTypes: {
       children: React.PropTypes.oneOfType([
         React.PropTypes.element,
         React.PropTypes.arrayOf(React.PropTypes.element)
       ]).isRequired,
+      cssClass: React.PropTypes.string,
       dashed: React.PropTypes.bool,
       style: React.PropTypes.object,
       summary: React.PropTypes.string.isRequired
     },
 
     makeId: function(prefix) {
       return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
     },
 
     render: function() {
       var cx = React.addons.classSet;
+      var extraCSSClass = {
+        "example": true
+      };
+      if (this.props.cssClass) {
+        extraCSSClass[this.props.cssClass] = true;
+      }
       return (
-        <div className="example">
+        <div className={cx(extraCSSClass)}>
           <h3 id={this.makeId()}>
             {this.props.summary}
             <a href={this.makeId("#")}>&nbsp;¶</a>
           </h3>
           <div className={cx({comp: true, dashed: this.props.dashed})}
                style={this.props.style}>
             {this.props.children}
           </div>
@@ -688,99 +708,117 @@
     render: function() {
       return (
         <ShowCase>
           <Section name="PanelView">
             <p className="note">
               <strong>Note:</strong> 332px wide.
             </p>
             <Example dashed={true} style={{width: "332px"}} summary="Re-sign-in view">
-              <SignInRequestView mozLoop={mockMozLoopRooms} />
+              <SignInRequestView mozLoop={mockMozLoopLoggedIn} />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Room list tab">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
-                         mozLoop={mockMozLoopRooms}
+                         mozLoop={mockMozLoopLoggedIn}
                          notifications={notifications}
                          roomStore={roomStore}
-                         selectedTab="rooms"
-                         userProfile={{email: "test@example.com"}} />
+                         selectedTab="rooms" />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Contact list tab">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
-                         mozLoop={mockMozLoopRooms}
+                         mozLoop={mockMozLoopLoggedIn}
                          notifications={notifications}
                          roomStore={roomStore}
-                         selectedTab="contacts"
-                         userProfile={{email: "test@example.com"}} />
+                         selectedTab="contacts" />
+            </Example>
+            <Example dashed={true} style={{width: "332px"}} summary="Contact list tab long email">
+              <PanelView client={mockClient}
+                         dispatcher={dispatcher}
+                         mozLoop={mockMozLoopLoggedInLongEmail}
+                         notifications={notifications}
+                         roomStore={roomStore}
+                         selectedTab="contacts" />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Error Notification">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={navigator.mozLoop}
                          notifications={errNotifications}
                          roomStore={roomStore} />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Error Notification - authenticated">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
-                         mozLoop={navigator.mozLoop}
+                         mozLoop={mockMozLoopLoggedIn}
                          notifications={errNotifications}
-                         roomStore={roomStore}
-                         userProfile={{email: "test@example.com"}} />
+                         roomStore={roomStore} />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Contact import success">
               <PanelView dispatcher={dispatcher}
-                         mozLoop={mockMozLoopRooms}
+                         mozLoop={mockMozLoopLoggedIn}
                          notifications={new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}])}
                          roomStore={roomStore}
-                         selectedTab="contacts"
-                         userProfile={{email: "test@example.com"}} />
+                         selectedTab="contacts" />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Contact import error">
               <PanelView dispatcher={dispatcher}
-                         mozLoop={mockMozLoopRooms}
+                         mozLoop={mockMozLoopLoggedIn}
                          notifications={new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}])}
                          roomStore={roomStore}
-                         selectedTab="contacts"
-                         userProfile={{email: "test@example.com"}} />
+                         selectedTab="contacts" />
+            </Example>
+          </Section>
+
+          <Section name="Availability Dropdown">
+            <p className="note">
+              <strong>Note:</strong> 332px wide.
+            </p>
+            <Example dashed={true} style={{width: "332px", height: "200px"}}
+                     summary="AvailabilityDropdown">
+              <AvailabilityDropdown />
+            </Example>
+            <Example cssClass="force-menu-show" dashed={true}
+                     style={{width: "332px", height: "200px"}}
+                     summary="AvailabilityDropdown Expanded">
+              <AvailabilityDropdown />
             </Example>
           </Section>
 
           <Section name="AcceptCallView">
             <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default / incoming video call">
               <div className="fx-embedded">
                 <AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
-                                mozLoop={mockMozLoopRooms} />
+                                mozLoop={mockMozLoopLoggedIn} />
               </div>
             </Example>
 
             <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default / incoming audio only call">
               <div className="fx-embedded">
                 <AcceptCallView callType={CALL_TYPES.AUDIO_ONLY}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
-                                mozLoop={mockMozLoopRooms} />
+                                mozLoop={mockMozLoopLoggedIn} />
               </div>
             </Example>
           </Section>
 
           <Section name="AcceptCallView-ActiveState">
             <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default">
               <div className="fx-embedded" >
                 <AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
-                                mozLoop={mockMozLoopRooms}
+                                mozLoop={mockMozLoopLoggedIn}
                                 showMenu={true} />
               </div>
             </Example>
           </Section>
 
           <Section name="ConversationToolbar">
             <h2>Desktop Conversation Window</h2>
             <div className="fx-embedded override-position">
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -114,16 +114,17 @@ let gSyncPane = {
 
   _init: function () {
     let topics = ["weave:service:login:error",
                   "weave:service:login:finish",
                   "weave:service:start-over:finish",
                   "weave:service:setup-complete",
                   "weave:service:logout:finish",
                   FxAccountsCommon.ONVERIFIED_NOTIFICATION,
+                  FxAccountsCommon.ONLOGIN_NOTIFICATION,
                   FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
                   ];
     let migrateTopic = "fxa-migration:state-changed";
 
     // Add the observers now and remove them on unload
     //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
     //        of `this`. Fix in a followup. (bug 583347)
     topics.forEach(function (topic) {
--- a/browser/devtools/definitions.js
+++ b/browser/devtools/definitions.js
@@ -28,23 +28,23 @@ loader.lazyGetter(this, "ScratchpadPanel
 const toolboxProps = "chrome://browser/locale/devtools/toolbox.properties";
 const inspectorProps = "chrome://browser/locale/devtools/inspector.properties";
 const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
 const debuggerProps = "chrome://browser/locale/devtools/debugger.properties";
 const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties";
 const shaderEditorProps = "chrome://browser/locale/devtools/shadereditor.properties";
 const canvasDebuggerProps = "chrome://browser/locale/devtools/canvasdebugger.properties";
 const webAudioEditorProps = "chrome://browser/locale/devtools/webaudioeditor.properties";
-const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
+const performanceProps = "chrome://browser/locale/devtools/performance.properties";
 const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
 const storageProps = "chrome://browser/locale/devtools/storage.properties";
 const scratchpadProps = "chrome://browser/locale/devtools/scratchpad.properties";
 
 loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
-loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps));
+loader.lazyGetter(this, "performanceStrings",() => Services.strings.createBundle(performanceProps));
 loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
 loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
 loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
 loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBundle(shaderEditorProps));
 loader.lazyGetter(this, "canvasDebuggerStrings", () => Services.strings.createBundle(canvasDebuggerProps));
 loader.lazyGetter(this, "webAudioEditorStrings", () => Services.strings.createBundle(webAudioEditorProps));
 loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps));
 loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
@@ -249,24 +249,24 @@ Tools.canvasDebugger = {
 Tools.performance = {
   id: "performance",
   ordinal: 7,
   icon: "chrome://browser/skin/devtools/tool-profiler.svg",
   invertIconForLightTheme: true,
   highlightedicon: "chrome://browser/skin/devtools/tool-profiler-active.svg",
   url: "chrome://browser/content/devtools/performance.xul",
   visibilityswitch: "devtools.performance.enabled",
-  label: l10n("profiler.label2", profilerStrings),
-  panelLabel: l10n("profiler.panelLabel2", profilerStrings),
+  label: l10n("performance.label", performanceStrings),
+  panelLabel: l10n("performance.panelLabel", performanceStrings),
   get tooltip() {
-    return l10n("profiler.tooltip3", profilerStrings,
+    return l10n("performance.tooltip", performanceStrings,
     "Shift+" + functionkey(this.key));
   },
-  accesskey: l10n("profiler.accesskey", profilerStrings),
-  key: l10n("profiler.commandkey2", profilerStrings),
+  accesskey: l10n("performance.accesskey", performanceStrings),
+  key: l10n("performance.commandkey", performanceStrings),
   modifiers: "shift",
   inMenu: true,
 
   isTargetSupported: function (target) {
     return target.hasActor("profiler");
   },
 
   build: function (frame, target) {
--- a/browser/devtools/performance/modules/global.js
+++ b/browser/devtools/performance/modules/global.js
@@ -2,21 +2,20 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
 
 /**
  * Localization convenience methods.
- + TODO: merge these into a single file: Bug 1082695.
  */
 const L10N = new ViewHelpers.MultiL10N([
-  "chrome://browser/locale/devtools/timeline.properties",
-  "chrome://browser/locale/devtools/profiler.properties"
+  "chrome://browser/locale/devtools/markers.properties",
+  "chrome://browser/locale/devtools/performance.properties"
 ]);
 
 /**
  * A list of preferences for this tool. The values automatically update
  * if somebody edits edits about:config or the prefs change somewhere else.
  */
 const PREFS = new ViewHelpers.Prefs("devtools.performance", {
   "show-platform-data": ["Bool", "ui.show-platform-data"],
--- a/browser/devtools/performance/modules/logic/marker-utils.js
+++ b/browser/devtools/performance/modules/logic/marker-utils.js
@@ -88,22 +88,18 @@ function getMarkerClassName (type) {
  * @return {Array<object>}
  */
 function getMarkerFields (marker) {
   let blueprint = getBlueprintFor(marker);
 
   // If blueprint.fields is a function, use that
   if (typeof blueprint.fields === "function") {
     let fields = blueprint.fields(marker);
-    // Add a ":" to the label since the localization files contain the ":"
-    // if not present. This should be changed, ugh.
     return Object.keys(fields || []).map(label => {
-      // TODO revisit localization strings for markers bug 1163763
-      let normalizedLabel = label.indexOf(":") !== -1 ? label : (label + ":");
-      return { label: normalizedLabel, value: fields[label] };
+      return { label, value: fields[label] };
     });
   }
 
   // Otherwise, iterate over the array
   return (blueprint.fields || []).reduce((fields, field) => {
     // Ensure this marker has this field present
     if (field.property in marker) {
       let label = field.label;
@@ -163,17 +159,17 @@ const DOM = {
   /**
    * Builds the duration element, like "Duration: 200ms".
    *
    * @param {Document} doc
    * @param {ProfileTimelineMarker} marker
    * @return {Element}
    */
   buildDuration: function (doc, marker) {
-    let label = L10N.getStr("timeline.markerDetail.duration");
+    let label = L10N.getStr("marker.field.duration");
     let start = L10N.getFormatStrWithNumbers("timeline.tick", marker.start);
     let end = L10N.getFormatStrWithNumbers("timeline.tick", marker.end);
     let duration = L10N.getFormatStrWithNumbers("timeline.tick", marker.end - marker.start);
     let el = DOM.buildNameValueLabel(doc, label, duration);
     el.classList.add("marker-details-duration");
     el.setAttribute("tooltiptext", `${start} → ${end}`);
     return el;
   },
@@ -212,17 +208,17 @@ const DOM = {
    *        string type - String identifier for type of stack ("stack", "startStack" or "endStack")
    *        number frameIndex - The index of the topmost stack frame.
    *        array frames - Array of stack frames.
    */
   buildStackTrace: function(doc, { type, frameIndex, frames }) {
     let container = doc.createElement("vbox");
     let labelName = doc.createElement("label");
     labelName.className = "plain marker-details-labelname";
-    labelName.setAttribute("value", L10N.getStr(`timeline.markerDetail.${type}`));
+    labelName.setAttribute("value", L10N.getStr(`marker.field.${type}`));
     container.setAttribute("type", type);
     container.className = "marker-details-stack";
     container.appendChild(labelName);
 
     let wasAsyncParent = false;
     while (frameIndex > 0) {
       let frame = frames[frameIndex];
       let url = frame.source;
@@ -230,17 +226,17 @@ const DOM = {
       let line = frame.line;
 
       // If the previous frame had an async parent, then the async
       // cause is in this frame and should be displayed.
       if (wasAsyncParent) {
         let asyncBox = doc.createElement("hbox");
         let asyncLabel = doc.createElement("label");
         asyncLabel.className = "devtools-monospace";
-        asyncLabel.setAttribute("value", L10N.getFormatStr("timeline.markerDetail.asyncStack",
+        asyncLabel.setAttribute("value", L10N.getFormatStr("marker.field.asyncStack",
                                                            frame.asyncCause));
         asyncBox.appendChild(asyncLabel);
         container.appendChild(asyncBox);
         wasAsyncParent = false;
       }
 
       let hbox = doc.createElement("hbox");
 
@@ -273,17 +269,17 @@ const DOM = {
         // which handles the view source.
         aNode.setAttribute("data-action", JSON.stringify({
           url, line, action: "view-source"
         }));
       }
 
       if (!displayName && !url) {
         let label = doc.createElement("label");
-        label.setAttribute("value", L10N.getStr("timeline.markerDetail.unknownFrame"));
+        label.setAttribute("value", L10N.getStr("marker.value.unknownFrame"));
         hbox.appendChild(label);
       }
 
       container.appendChild(hbox);
 
       if (frame.asyncParent) {
         frameIndex = frame.asyncParent;
         wasAsyncParent = true;
@@ -296,54 +292,54 @@ const DOM = {
   }
 };
 
 /**
  * Mapping of JS marker causes to a friendlier form. Only
  * markers that are considered "from content" should be labeled here.
  */
 const JS_MARKER_MAP = {
-  "<script> element":          "Script Tag",
+  "<script> element":          L10N.getStr("marker.label.javascript.scriptElement"),
+  "promise callback":          L10N.getStr("marker.label.javascript.promiseCallback"),
+  "promise initializer":       L10N.getStr("marker.label.javascript.promiseInit"),
+  "Worker runnable":           L10N.getStr("marker.label.javascript.workerRunnable"),
+  "javascript: URI":           L10N.getStr("marker.label.javascript.jsURI"),
+  // The difference between these two event handler markers are differences
+  // in their WebIDL implementation, so distinguishing them is not necessary.
+  "EventHandlerNonNull":       L10N.getStr("marker.label.javascript.eventHandler"),
+  "EventListener.handleEvent": L10N.getStr("marker.label.javascript.eventHandler"),
+  // These markers do not get L10N'd because they're JS names.
   "setInterval handler":       "setInterval",
   "setTimeout handler":        "setTimeout",
   "FrameRequestCallback":      "requestAnimationFrame",
-  "promise callback":          "Promise Callback",
-  "promise initializer":       "Promise Init",
-  "Worker runnable":           "Worker",
-  "javascript: URI":           "JavaScript URI",
-  // The difference between these two event handler markers are differences
-  // in their WebIDL implementation, so distinguishing them is not necessary.
-  "EventHandlerNonNull":       "Event Handler",
-  "EventListener.handleEvent": "Event Handler",
 };
 
 /**
  * A series of formatters used by the blueprint.
  */
 const Formatters = {
   /**
    * Uses the marker name as the label for markers that do not have
    * a blueprint entry. Uses "Other" in the marker filter menu.
    */
   UnknownLabel: function (marker={}) {
-    return marker.name || L10N.getStr("timeline.label.unknown");
+    return marker.name || L10N.getStr("marker.label.unknown");
   },
 
   GCLabel: function (marker={}) {
-    let label = L10N.getStr("timeline.label.garbageCollection");
     // Only if a `nonincrementalReason` exists, do we want to label
     // this as a non incremental GC event.
     if ("nonincrementalReason" in marker) {
-      label = `${label} (Non-incremental)`;
+      return L10N.getStr("marker.label.garbageCollection.nonIncremental");
     }
-    return label;
+    return L10N.getStr("marker.label.garbageCollection");
   },
 
   JSLabel: function (marker={}) {
-    let generic = L10N.getStr("timeline.label.javascript2");
+    let generic = L10N.getStr("marker.label.javascript");
     if ("causeName" in marker) {
       return JS_MARKER_MAP[marker.causeName] || generic;
     }
     return generic;
   },
 
   DOMJSLabel: function (marker={}) {
     return `Event (${marker.type})`;
@@ -352,50 +348,54 @@ const Formatters = {
   /**
    * Returns a hash for computing a fields object for a JS marker. If the cause
    * is considered content (so an entry exists in the JS_MARKER_MAP), do not display it
    * since it's redundant with the label. Otherwise for Gecko code, either display
    * the cause, or "(Gecko)", depending on if "show-platform-data" is set.
    */
   JSFields: function (marker) {
     if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) {
-      return { Reason: PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL };
+      let cause = PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL;
+      return {
+        [L10N.getStr("marker.field.causeName")]: cause
+      };
     }
   },
 
   DOMEventFields: function (marker) {
     let fields = Object.create(null);
     if ("type" in marker) {
-      fields[L10N.getStr("timeline.markerDetail.DOMEventType")] = marker.type;
+      fields[L10N.getStr("marker.field.DOMEventType")] = marker.type;
     }
     if ("eventPhase" in marker) {
       let phase;
       if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) {
-        phase = L10N.getStr("timeline.markerDetail.DOMEventTargetPhase");
+        phase = L10N.getStr("marker.value.DOMEventTargetPhase");
       } else if (marker.eventPhase === Ci.nsIDOMEvent.CAPTURING_PHASE) {
-        phase = L10N.getStr("timeline.markerDetail.DOMEventCapturingPhase");
+        phase = L10N.getStr("marker.value.DOMEventCapturingPhase");
       } else if (marker.eventPhase === Ci.nsIDOMEvent.BUBBLING_PHASE) {
-        phase = L10N.getStr("timeline.markerDetail.DOMEventBubblingPhase");
+        phase = L10N.getStr("marker.value.DOMEventBubblingPhase");
       }
-      fields[L10N.getStr("timeline.markerDetail.DOMEventPhase")] = phase;
+      fields[L10N.getStr("marker.field.DOMEventPhase")] = phase;
     }
     return fields;
   },
 
   StylesFields: function (marker) {
     if ("restyleHint" in marker) {
-      return { "Restyle Hint": marker.restyleHint.replace(/eRestyle_/g, "") };
+      return {
+        [L10N.getStr("marker.field.restyleHint")]: marker.restyleHint.replace(/eRestyle_/g, "")
+      };
     }
   },
 
   CycleCollectionFields: function (marker) {
-    let Type = PREFS["show-platform-data"]
-        ? marker.name
-        : marker.name.replace(/nsCycleCollector::/g, "");
-    return { Type };
+    return {
+      [L10N.getStr("marker.field.type")]: marker.name.replace(/nsCycleCollector::/g, "")
+    };
   },
 };
 
 /**
  * Takes a marker and returns the definition for that marker type,
  * falling back to the UNKNOWN definition if undefined.
  *
  * @param {Marker} marker
--- a/browser/devtools/performance/modules/logic/recording-model.js
+++ b/browser/devtools/performance/modules/logic/recording-model.js
@@ -366,19 +366,18 @@ RecordingModel.prototype = {
         break;
       }
       // Accumulate allocation sites into an array. Furthermore, the timestamps
       // do not have a zero epoch, and are microseconds instead of milliseconds,
       // so offset all of them by the start time, also converting from µs to ms.
       case "allocations": {
         if (!config.withAllocations) { break; }
         let [{ sites, timestamps, frames, counts }] = data;
-        let timeOffset = this._memoryStartTime * 1000;
-        let timeScale = 1000;
-        RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset, timeScale);
+        let timeOffset = this._memoryStartTime;
+        RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset);
         pushAll(this._allocations.sites, sites);
         pushAll(this._allocations.timestamps, timestamps);
         pushAll(this._allocations.frames, frames);
         pushAll(this._allocations.counts, counts);
         break;
       }
     }
   },
--- a/browser/devtools/performance/modules/logic/recording-utils.js
+++ b/browser/devtools/performance/modules/logic/recording-utils.js
@@ -101,17 +101,19 @@ function offsetMarkerTimes(markers, time
  * @param number timeOffset
  *        The amount of time to offset by (in milliseconds).
  * @param number timeScale
  *        The factor to scale by, after offsetting.
  */
 function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
   for (let i = 0, len = timestamps.length; i < len; i++) {
     timestamps[i] -= timeOffset;
-    timestamps[i] /= timeScale;
+    if (timeScale) {
+      timestamps[i] /= timeScale;
+    }
   }
 }
 
 /**
  * Cache used in `RecordingUtils.getProfileThreadFromAllocations`.
  */
 let gProfileThreadFromAllocationCache = new WeakMap();
 
--- a/browser/devtools/performance/modules/markers.js
+++ b/browser/devtools/performance/modules/markers.js
@@ -5,17 +5,17 @@
 
 const { L10N } = require("devtools/performance/global");
 const { Formatters } = require("devtools/performance/marker-utils");
 
 /**
  * A simple schema for mapping markers to the timeline UI. The keys correspond
  * to marker names, while the values are objects with the following format:
  *
- * - group: The row index in the timeline overview graph; multiple markers
+ * - group: The row index in the overview graph; multiple markers
  *          can be added on the same row. @see <overview.js/buildGraphImage>
  * - label: The label used in the waterfall to identify the marker. Can be a
  *          string or just a function that accepts the marker and returns a
  *          string, if you want to use a dynamic property for the main label.
  *          If you use a function for a label, it *must* handle the case where
  *          no marker is provided for a main label to describe all markers of
  *          this type.
  * - colorName: The label of the DevTools color used for this marker. If
@@ -55,91 +55,91 @@ const TIMELINE_BLUEPRINT = {
     colorName: "graphs-grey",
     label: Formatters.UnknownLabel,
   },
 
   /* Group 0 - Reflow and Rendering pipeline */
   "Styles": {
     group: 0,
     colorName: "graphs-purple",
-    label: L10N.getStr("timeline.label.styles2"),
+    label: L10N.getStr("marker.label.styles"),
     fields: Formatters.StylesFields,
   },
   "Reflow": {
     group: 0,
     colorName: "graphs-purple",
-    label: L10N.getStr("timeline.label.reflow2"),
+    label: L10N.getStr("marker.label.reflow"),
   },
   "Paint": {
     group: 0,
     colorName: "graphs-green",
-    label: L10N.getStr("timeline.label.paint"),
+    label: L10N.getStr("marker.label.paint"),
   },
 
   /* Group 1 - JS */
   "DOMEvent": {
     group: 1,
     colorName: "graphs-yellow",
-    label: L10N.getStr("timeline.label.domevent"),
+    label: L10N.getStr("marker.label.domevent"),
     fields: Formatters.DOMEventFields,
   },
   "Javascript": {
     group: 1,
     colorName: "graphs-yellow",
     label: Formatters.JSLabel,
     fields: Formatters.JSFields
   },
   "Parse HTML": {
     group: 1,
     colorName: "graphs-yellow",
-    label: L10N.getStr("timeline.label.parseHTML"),
+    label: L10N.getStr("marker.label.parseHTML"),
   },
   "Parse XML": {
     group: 1,
     colorName: "graphs-yellow",
-    label: L10N.getStr("timeline.label.parseXML"),
+    label: L10N.getStr("marker.label.parseXML"),
   },
   "GarbageCollection": {
     group: 1,
     colorName: "graphs-red",
     label: Formatters.GCLabel,
     fields: [
-      { property: "causeName", label: "Reason:" },
-      { property: "nonincrementalReason", label: "Non-incremental Reason:" }
+      { property: "causeName", label: L10N.getStr("marker.field.causeName") },
+      { property: "nonincrementalReason", label: L10N.getStr("marker.field.nonIncrementalCause") }
     ],
   },
   "nsCycleCollector::Collect": {
     group: 1,
     colorName: "graphs-red",
-    label: "Cycle Collection",
+    label: L10N.getStr("marker.label.cycleCollection"),
     fields: Formatters.CycleCollectionFields,
   },
   "nsCycleCollector::ForgetSkippable": {
     group: 1,
     colorName: "graphs-red",
-    label: "Cycle Collection",
+    label: L10N.getStr("marker.label.cycleCollection.forgetSkippable"),
     fields: Formatters.CycleCollectionFields,
   },
 
   /* Group 2 - User Controlled */
   "ConsoleTime": {
     group: 2,
     colorName: "graphs-blue",
-    label: sublabelForProperty(L10N.getStr("timeline.label.consoleTime"), "causeName"),
+    label: sublabelForProperty(L10N.getStr("marker.label.consoleTime"), "causeName"),
     fields: [{
       property: "causeName",
-      label: L10N.getStr("timeline.markerDetail.consoleTimerName")
+      label: L10N.getStr("marker.field.consoleTimerName")
     }],
     nestable: false,
     collapsible: false,
   },
   "TimeStamp": {
     group: 2,
     colorName: "graphs-blue",
-    label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"),
+    label: sublabelForProperty(L10N.getStr("marker.label.timestamp"), "causeName"),
     fields: [{
       property: "causeName",
       label: "Label:"
     }],
     collapsible: false,
   },
 };
 
--- a/browser/devtools/performance/modules/widgets/graphs.js
+++ b/browser/devtools/performance/modules/widgets/graphs.js
@@ -19,18 +19,18 @@ loader.lazyRequireGetter(this, "EventEmi
   "devtools/toolkit/event-emitter");
 
 loader.lazyRequireGetter(this, "colorUtils",
   "devtools/css-color", true);
 loader.lazyRequireGetter(this, "getColor",
   "devtools/shared/theme", true);
 loader.lazyRequireGetter(this, "ProfilerGlobal",
   "devtools/performance/global");
-loader.lazyRequireGetter(this, "TimelineGlobal",
-  "devtools/performance/global");
+loader.lazyRequireGetter(this, "L10N",
+  "devtools/performance/global", true);
 loader.lazyRequireGetter(this, "MarkersOverview",
   "devtools/performance/markers-overview", true);
 
 /**
  * For line graphs
  */
 const HEIGHT = 35; // px
 const STROKE_WIDTH = 1; // px
@@ -119,17 +119,17 @@ FramerateGraph.prototype = Heritage.exte
 
 /**
  * Constructor for the memory graph. Inherits from PerformanceGraph.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the overview.
  */
 function MemoryGraph(parent) {
-  PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
+  PerformanceGraph.call(this, parent, L10N.getStr("graphs.memory"));
 }
 
 MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
   mainColor: MEMORY_GRAPH_COLOR_NAME,
   setPerformanceData: function ({ duration, memory }) {
     this.dataDuration = duration;
     return this.setData(memory);
   }
--- a/browser/devtools/performance/modules/widgets/marker-details.js
+++ b/browser/devtools/performance/modules/widgets/marker-details.js
@@ -6,18 +6,16 @@
 /**
  * This file contains the rendering code for the marker sidebar.
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
-loader.lazyRequireGetter(this, "L10N",
-  "devtools/performance/global", true);
 loader.lazyRequireGetter(this, "MarkerUtils",
   "devtools/performance/marker-utils");
 
 /**
  * A detailed view for one single marker.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the view.
--- a/browser/devtools/performance/performance-view.js
+++ b/browser/devtools/performance/performance-view.js
@@ -168,17 +168,17 @@ let PerformanceView = {
     // Be a little flexible on the buffer status, although not sure how
     // this could happen, as RecordingModel clamps.
     if (percent >= 99) {
       $container.setAttribute("buffer-status", "full");
     } else {
       $container.setAttribute("buffer-status", "in-progress");
     }
 
-    $bufferLabel.value = `Buffer ${percent}% full`;
+    $bufferLabel.value = L10N.getFormatStr("profiler.bufferFull", percent);
     this.emit(EVENTS.UI_BUFFER_UPDATED, percent);
   },
 
   /**
    * Toggles the `locked` attribute on the record buttons based
    * on `lock`.
    *
    * @param {boolean} lock
@@ -284,18 +284,17 @@ let PerformanceView = {
     }
   },
 
   /**
    * Handler for clicking the import button.
    */
   _onImportButtonClick: function(e) {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-    // TODO localize? in bug 1163763
-    fp.init(window, "Import recording…", Ci.nsIFilePicker.modeOpen);
+    fp.init(window, L10N.getStr("recordingsList.importDialogTitle"), Ci.nsIFilePicker.modeOpen);
     fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
     fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
 
     if (fp.show() == Ci.nsIFilePicker.returnOK) {
       this.emit(EVENTS.UI_IMPORT_RECORDING, fp.file);
     }
   },
 
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -3,18 +3,18 @@
    - 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/. -->
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/performance.css" type="text/css"?>
 <!DOCTYPE window [
-  <!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
-  %profilerDTD;
+  <!ENTITY % performanceDTD SYSTEM "chrome://browser/locale/devtools/performance.dtd">
+  %performanceDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script src="chrome://browser/content/devtools/theme-switching.js"/>
   <script type="application/javascript" src="performance/performance-controller.js"/>
   <script type="application/javascript" src="performance/performance-view.js"/>
   <script type="application/javascript" src="performance/views/overview.js"/>
   <script type="application/javascript" src="performance/views/toolbar.js"/>
@@ -31,150 +31,150 @@
   <script type="application/javascript" src="performance/views/frames-list.js"/>
 
   <popupset id="performance-options-popupset">
     <menupopup id="performance-filter-menupopup"/>
     <menupopup id="performance-options-menupopup">
       <menuitem id="option-show-platform-data"
                 type="checkbox"
                 data-pref="show-platform-data"
-                label="&profilerUI.showPlatformData;"
-                tooltiptext="&profilerUI.showPlatformData.tooltiptext;"/>
+                label="&performanceUI.showPlatformData;"
+                tooltiptext="&performanceUI.showPlatformData.tooltiptext;"/>
       <menuitem id="option-enable-memory"
                 class="experimental-option"
                 type="checkbox"
                 data-pref="enable-memory"
-                label="&profilerUI.enableMemory;"
-                tooltiptext="&profilerUI.enableMemory.tooltiptext;"/>
+                label="&performanceUI.enableMemory;"
+                tooltiptext="&performanceUI.enableMemory.tooltiptext;"/>
       <menuitem id="option-enable-allocations"
                 class="experimental-option"
                 type="checkbox"
                 data-pref="enable-allocations"
-                label="&profilerUI.enableAllocations;"
-                tooltiptext="&profilerUI.enableAllocations.tooltiptext;"/>
+                label="&performanceUI.enableAllocations;"
+                tooltiptext="&performanceUI.enableAllocations.tooltiptext;"/>
       <menuitem id="option-enable-framerate"
                 type="checkbox"
                 data-pref="enable-framerate"
-                label="&profilerUI.enableFramerate;"
-                tooltiptext="&profilerUI.enableFramerate.tooltiptext;"/>
+                label="&performanceUI.enableFramerate;"
+                tooltiptext="&performanceUI.enableFramerate.tooltiptext;"/>
       <menuitem id="option-invert-call-tree"
                 type="checkbox"
                 data-pref="invert-call-tree"
-                label="&profilerUI.invertTree;"
-                tooltiptext="&profilerUI.invertTree.tooltiptext;"/>
+                label="&performanceUI.invertTree;"
+                tooltiptext="&performanceUI.invertTree.tooltiptext;"/>
       <menuitem id="option-invert-flame-graph"
                 type="checkbox"
                 data-pref="invert-flame-graph"
-                label="&profilerUI.invertFlameGraph;"
-                tooltiptext="&profilerUI.invertFlameGraph.tooltiptext;"/>
+                label="&performanceUI.invertFlameGraph;"
+                tooltiptext="&performanceUI.invertFlameGraph.tooltiptext;"/>
       <menuitem id="option-flatten-tree-recursion"
                 type="checkbox"
                 data-pref="flatten-tree-recursion"
-                label="&profilerUI.flattenTreeRecursion;"
-                tooltiptext="&profilerUI.flattenTreeRecursion.tooltiptext;"/>
+                label="&performanceUI.flattenTreeRecursion;"
+                tooltiptext="&performanceUI.flattenTreeRecursion.tooltiptext;"/>
       <menuitem id="option-enable-jit-optimizations"
                 class="experimental-option"
                 type="checkbox"
                 data-pref="enable-jit-optimizations"
-                label="&profilerUI.enableJITOptimizations;"
-                tooltiptext="&profilerUI.enableJITOptimizations.tooltiptext;"/>
+                label="&performanceUI.enableJITOptimizations;"
+                tooltiptext="&performanceUI.enableJITOptimizations.tooltiptext;"/>
     </menupopup>
   </popupset>
 
   <hbox class="theme-body" flex="1">
 
     <!-- Sidebar: controls and recording list -->
     <vbox id="recordings-pane">
       <toolbar id="recordings-toolbar"
                class="devtools-toolbar">
         <hbox id="recordings-controls"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="main-record-button"
                          class="devtools-toolbarbutton record-button"
-                         tooltiptext="&profilerUI.recordButton2.tooltip;"/>
+                         tooltiptext="&performanceUI.recordButton.tooltip;"/>
           <toolbarbutton id="import-button"
                          class="devtools-toolbarbutton"
-                         label="&profilerUI.importButton;"/>
+                         label="&performanceUI.importButton;"/>
           <toolbarbutton id="clear-button"
                          class="devtools-toolbarbutton"
-                         label="&profilerUI.clearButton;"/>
+                         label="&performanceUI.clearButton;"/>
         </hbox>
       </toolbar>
       <vbox id="recordings-list" flex="1"/>
     </vbox>
 
     <!-- Main panel content -->
     <vbox id="performance-pane" flex="1">
 
       <!-- Top toolbar controls -->
       <toolbar id="performance-toolbar"
                class="devtools-toolbar">
         <hbox id="performance-toolbar-controls-other"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="filter-button"
                          class="devtools-toolbarbutton"
                          popup="performance-filter-menupopup"
-                         tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
+                         tooltiptext="&performanceUI.options.filter.tooltiptext;"/>
         </hbox>
         <hbox id="performance-toolbar-controls-detail-views"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="select-waterfall-view"
                          class="devtools-toolbarbutton devtools-button"
-                         label="Waterfall"
+                         label="&performanceUI.toolbar.waterfall;"
                          hidden="true"
                          data-view="waterfall" />
           <toolbarbutton id="select-js-calltree-view"
                          class="devtools-toolbarbutton devtools-button"
-                         label="Call Tree"
+                         label="&performanceUI.toolbar.js-calltree;"
                          hidden="true"
                          data-view="js-calltree" />
           <toolbarbutton id="select-js-flamegraph-view"
                          class="devtools-toolbarbutton devtools-button"
-                         label="Flame Chart"
+                         label="&performanceUI.toolbar.js-flamegraph;"
                          hidden="true"
                          data-view="js-flamegraph" />
           <toolbarbutton id="select-memory-calltree-view"
                          class="devtools-toolbarbutton devtools-button"
-                         label="Allocations Tree"
+                         label="&performanceUI.toolbar.memory-calltree;"
                          hidden="true"
                          data-view="memory-calltree" />
           <toolbarbutton id="select-memory-flamegraph-view"
                          class="devtools-toolbarbutton devtools-button"
-                         label="Allocations Chart"
+                         label="&performanceUI.toolbar.memory-flamegraph;"
                          hidden="true"
                          data-view="memory-flamegraph" />
           <toolbarbutton id="select-optimizations-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="Optimizations"
                          hidden="true"
                          data-view="optimizations" />
         </hbox>
         <spacer flex="1"></spacer>
         <hbox id="performance-toolbar-controls-options"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="performance-options-button"
                          class="devtools-toolbarbutton devtools-option-toolbarbutton"
                          popup="performance-options-menupopup"
-                         tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
+                         tooltiptext="&performanceUI.options.gear.tooltiptext;"/>
         </hbox>
       </toolbar>
 
       <!-- Recording contents and general notice messages -->
       <deck id="performance-view" flex="1">
 
         <!-- "Empty" notice, shown when there's no recordings available -->
         <hbox id="empty-notice"
               class="notice-container"
               align="center"
               pack="center"
               flex="1">
           <hbox class="devtools-toolbarbutton-group"
                 pack="center">
             <toolbarbutton class="devtools-toolbarbutton record-button"
-                           label="&profilerUI.startRecording;"
+                           label="&performanceUI.startRecording;"
                            standalone="true"/>
           </hbox>
         </hbox>
 
         <!-- Recording contents -->
         <vbox id="performance-view-content" flex="1">
 
           <!-- Overview graphs -->
@@ -188,69 +188,65 @@
           <deck id="details-pane-container" flex="1">
 
             <!-- "Loading" notice, shown when a recording is being loaded -->
             <hbox id="loading-notice"
                   class="notice-container devtools-throbber"
                   align="center"
                   pack="center"
                   flex="1">
-              <label value="&profilerUI.loadingNotice;"/>
+              <label value="&performanceUI.loadingNotice;"/>
             </hbox>
 
             <!-- "Recording" notice, shown when a recording is in progress -->
             <vbox id="recording-notice"
                   class="notice-container"
                   align="center"
                   pack="center"
                   flex="1">
               <hbox class="devtools-toolbarbutton-group"
                     pack="center">
                 <toolbarbutton class="devtools-toolbarbutton record-button"
-                               label="&profilerUI.stopRecording;"
+                               label="&performanceUI.stopRecording;"
                                standalone="true"/>
               </hbox>
-              <label class="realtime-disabled-message">
-                Realtime recording data disabled on non-multiprocess Firefox.
-              </label>
-              <label class="realtime-disabled-on-e10s-message">
-                Enable multiprocess Firefox in preferences for rendering recording data in realtime.
-              </label>
+              <label class="realtime-disabled-message"
+                     value="&performanceUI.disabledRealTime.nonE10SBuild;"/>
+              <label class="realtime-disabled-on-e10s-message"
+                     value="&performanceUI.disabledRealTime.disabledE10S;"/>
               <label class="buffer-status-message"
-                     tooltiptext="&profilerUI.bufferStatusTooltip;"/>
+                     tooltiptext="&performanceUI.bufferStatusTooltip;"/>
               <label class="buffer-status-message-full"
-                     value="&profilerUI.bufferStatusFull;"/>
+                     value="&performanceUI.bufferStatusFull;"/>
             </vbox>
 
             <!-- "Console" notice, shown when a console recording is in progress -->
             <vbox id="console-recording-notice"
                   class="notice-container"
                   align="center"
                   pack="center"
                   flex="1">
               <hbox class="console-profile-recording-notice">
-                <label value="&profilerUI.console.recordingNoticeStart;" />
+                <label value="&performanceUI.console.recordingNoticeStart;" />
                 <label class="console-profile-command" />
-                <label value="&profilerUI.console.recordingNoticeEnd;" />
+                <label value="&performanceUI.console.recordingNoticeEnd;" />
               </hbox>
               <hbox class="console-profile-stop-notice">
-                <label value="&profilerUI.console.stopCommandStart;" />
+                <label value="&performanceUI.console.stopCommandStart;" />
                 <label class="console-profile-command" />
-                <label value="&profilerUI.console.stopCommandEnd;" />
+                <label value="&performanceUI.console.stopCommandEnd;" />
               </hbox>
-              <label class="realtime-disabled-message">
-                Realtime recording data disabled on non-multiprocess Firefox.
-              </label>
-              <label class="realtime-disabled-on-e10s-message">
-                Enable multiprocess Firefox in preferences for rendering recording data in realtime.
-              </label>
+              <label class="realtime-disabled-message"
+                     value="&performanceUI.disabledRealTime.nonE10SBuild;"/>
+              <label class="realtime-disabled-on-e10s-message"
+                     value="&performanceUI.disabledRealTime.disabledE10S;"/>
               <label class="buffer-status-message"
-                     tooltiptext="&profilerUI.bufferStatusTooltip;"/>
+                     tooltiptext="&performanceUI.bufferStatusTooltip;"/>
               <label class="buffer-status-message-full"
-                     value="&profilerUI.bufferStatusFull;"/>
+                     value="&performanceUI.bufferStatusFull;"/>
             </vbox>
 
             <!-- Detail views -->
             <deck id="details-pane" flex="1">
 
               <!-- Waterfall -->
               <hbox id="waterfall-view" flex="1">
                 <vbox flex="1">
@@ -264,69 +260,69 @@
 
               <!-- JS Tree and JIT view -->
               <hbox id="js-profile-view" flex="1">
                 <vbox id="js-calltree-view" flex="1">
                   <hbox class="call-tree-headers-container">
                     <label class="plain call-tree-header"
                            type="duration"
                            crop="end"
-                           value="&profilerUI.table.totalDuration2;"
-                           tooltiptext="&profilerUI.table.totalDuration.tooltip;"/>
+                           value="&performanceUI.table.totalDuration;"
+                           tooltiptext="&performanceUI.table.totalDuration.tooltip;"/>
                     <label class="plain call-tree-header"
                            type="percentage"
                            crop="end"
-                           value="&profilerUI.table.totalPercentage;"
-                           tooltiptext="&profilerUI.table.totalPercentage.tooltip;"/>
+                           value="&performanceUI.table.totalPercentage;"
+                           tooltiptext="&performanceUI.table.totalPercentage.tooltip;"/>
                     <label class="plain call-tree-header"
                            type="self-duration"
                            crop="end"
-                           value="&profilerUI.table.selfDuration2;"
-                           tooltiptext="&profilerUI.table.selfDuration.tooltip;"/>
+                           value="&performanceUI.table.selfDuration;"
+                           tooltiptext="&performanceUI.table.selfDuration.tooltip;"/>
                     <label class="plain call-tree-header"
                            type="self-percentage"
                            crop="end"
-                           value="&profilerUI.table.selfPercentage;"
-                           tooltiptext="&profilerUI.table.selfPercentage.tooltip;"/>
+                           value="&performanceUI.table.selfPercentage;"
+                           tooltiptext="&performanceUI.table.selfPercentage.tooltip;"/>
                     <label class="plain call-tree-header"
                            type="samples"
                            crop="end"
-                           value="&profilerUI.table.samples;"
-                           tooltiptext="&profilerUI.table.samples.tooltip;"/>
+                           value="&performanceUI.table.samples;"
+                           tooltiptext="&performanceUI.table.samples.tooltip;"/>
                     <label class="plain call-tree-header"
                            type="function"
                            crop="end"
-                           value="&profilerUI.table.function;"
-                           tooltiptext="&profilerUI.table.function.tooltip;"/>
+                           value="&performanceUI.table.function;"
+                           tooltiptext="&performanceUI.table.function.tooltip;"/>
                   </hbox>
                   <vbox class="call-tree-cells-container" flex="1"/>
                 </vbox>
               </hbox>
 
               <!-- JS FlameChart -->
               <hbox id="js-flamegraph-view" flex="1">
               </hbox>
 
               <!-- Memory Tree -->
               <vbox id="memory-calltree-view" flex="1">
                 <hbox class="call-tree-headers-container">
                   <label class="plain call-tree-header"
                          type="allocations"
                          crop="end"
-                         value="&profilerUI.table.totalAlloc1;"
-                         tooltiptext="&profilerUI.table.totalAlloc.tooltip;"/>
+                         value="&performanceUI.table.totalAlloc;"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
                   <label class="plain call-tree-header"
                          type="self-allocations"
                          crop="end"
-                         value="&profilerUI.table.selfAlloc1;"
-                         tooltiptext="&profilerUI.table.selfAlloc.tooltip;"/>
+                         value="&performanceUI.table.selfAlloc;"
+                         tooltiptext="&performanceUI.table.selfAlloc.tooltip;"/>
                   <label class="plain call-tree-header"
                          type="function"
                          crop="end"
-                         value="&profilerUI.table.function;"/>
+                         value="&performanceUI.table.function;"/>
                 </hbox>
                 <vbox class="call-tree-cells-container" flex="1"/>
               </vbox>
 
               <!-- Memory FlameChart -->
               <hbox id="memory-flamegraph-view" flex="1">
               </hbox>
 
@@ -347,17 +343,17 @@
                   <tabpanels flex="1">
 
                     <!-- Optimizations Panel -->
                     <tabpanel id="optimizations-tabpanel"
                               class="tabpanel-content">
                       <vbox id="jit-optimizations-view">
                         <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
                           <hbox id="jit-optimizations-header">
-                            <span class="jit-optimizations-title">&profilerUI.JITOptimizationsTitle;</span>
+                            <span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
                             <span class="header-function-name" />
                             <span class="header-file opt-url debugger-link" />
                             <span class="header-line opt-line" />
                           </hbox>
                         </toolbar>
                         <vbox id="jit-optimizations-raw-view"></vbox>
                       </vbox>
                     </tabpanel>
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
+  doc_allocs.html
   doc_force_cc.html
   doc_force_gc.html
   doc_innerHTML.html
   doc_markers.html
   doc_simple-test.html
   head.js
 
 # Commented out tests are profiler tests
--- a/browser/devtools/performance/test/browser_perf-details-memory-calltree-render.js
+++ b/browser/devtools/performance/test/browser_perf-details-memory-calltree-render.js
@@ -1,32 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the memory call tree view renders content after recording.
  */
 function* spawnTest() {
-  let { panel } = yield initPerformance(SIMPLE_URL);
-  let { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
+  let { panel } = yield initPerformance(ALLOCS_URL);
+  let { EVENTS, $$, PerformanceController, DetailsView, MemoryCallTreeView } = panel.panelWin;
 
   // Enable memory to test.
   Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
 
   yield startRecording(panel);
-  yield busyWait(100);
+  yield waitUntil(() => PerformanceController.getCurrentRecording().getAllocations().timestamps.length);
   yield stopRecording(panel);
 
   let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
   yield DetailsView.selectView("memory-calltree");
   ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
   yield rendered;
 
   ok(true, "MemoryCallTreeView rendered after recording is stopped.");
 
+  ok($$("#memory-calltree-view .call-tree-item").length, "there are several allocations rendered.");
+
   yield startRecording(panel);
   yield busyWait(100);
 
   rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
   yield stopRecording(panel);
   yield rendered;
 
   ok(true, "MemoryCallTreeView rendered again after recording completed a second time.");
--- a/browser/devtools/performance/test/browser_perf-loading-01.js
+++ b/browser/devtools/performance/test/browser_perf-loading-01.js
@@ -4,21 +4,17 @@
 /**
  * Tests that the recordings view shows the right label while recording, after
  * recording, and once the record has loaded.
  */
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
   let { RecordingsView, PerformanceController, PerformanceView,
-        EVENTS, $, L10N, ViewHelpers } = panel.panelWin;
-
-  // This should be removed with bug 1163763.
-  let DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
-  let DBG_L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
+        EVENTS, $, L10N } = panel.panelWin;
 
   info("Start to record");
   yield startRecording(panel);
   let durationNode = $(".recording-item-duration",
     RecordingsView.selectedItem.target);
 
   is(durationNode.getAttribute("value"),
     L10N.getStr("recordingsList.recordingLabel"),
@@ -29,17 +25,17 @@ let test = Task.async(function*() {
   let willStop = PerformanceController.once(EVENTS.RECORDING_WILL_STOP);
   let hasStopped = PerformanceController.once(EVENTS.RECORDING_STOPPED);
 
   click(panel.panelWin, $("#main-record-button"));
   yield clicked;
   yield willStop;
 
   is(durationNode.getAttribute("value"),
-    DBG_L10N.getStr("loadingText"),
+    L10N.getStr("recordingsList.loadingLabel"),
     "The duration node should show the 'loading' message while stopping");
 
   let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
   yield hasStopped;
   yield stateChanged;
 
   let duration = RecordingsView.selectedItem.attachment.getDuration().toFixed(0);
   is(durationNode.getAttribute("value"),
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/doc_allocs.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Performance test page</title>
+  </head>
+
+  <body>
+    <script type="text/javascript">
+      var allocs = [];
+      function test() {
+        for (var i = 0; i < 10; i++) {
+          allocs.push({});
+        }
+      }
+
+      // Prevent this script from being garbage collected.
+      window.setInterval(test, 1);
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -19,16 +19,17 @@ let { getPerformanceFront, PerformanceFr
 let TargetFactory = devtools.TargetFactory;
 
 let mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js"
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/performance/test/";
 const SIMPLE_URL = EXAMPLE_URL + "doc_simple-test.html";
 const MARKERS_URL = EXAMPLE_URL + "doc_markers.html";
+const ALLOCS_URL = EXAMPLE_URL + "doc_allocs.html";
 
 const MEMORY_SAMPLE_PROB_PREF = "devtools.performance.memory.sample-probability";
 const MEMORY_MAX_LOG_LEN_PREF = "devtools.performance.memory.max-log-length";
 const PROFILER_BUFFER_SIZE_PREF = "devtools.performance.profiler.buffer-size";
 const PROFILER_SAMPLE_RATE_PREF = "devtools.performance.profiler.sample-frequency-khz";
 
 const FRAMERATE_PREF = "devtools.performance.ui.enable-framerate";
 const MEMORY_PREF = "devtools.performance.ui.enable-memory";
--- a/browser/devtools/performance/test/unit/test_marker-utils.js
+++ b/browser/devtools/performance/test/unit/test_marker-utils.js
@@ -42,30 +42,30 @@ add_task(function () {
   equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)",
     "Correctly obfuscates JS markers when platform data is off.");
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
   equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "Some Platform Field",
     "Correctly deobfuscates JS markers when platform data is on.");
 
   equal(Utils.getMarkerClassName("Javascript"), "Function Call",
     "getMarkerClassName() returns correct string when defined via function");
-  equal(Utils.getMarkerClassName("GarbageCollection"), "GC Event",
+  equal(Utils.getMarkerClassName("GarbageCollection"), "Incremental GC",
     "getMarkerClassName() returns correct string when defined via function");
   equal(Utils.getMarkerClassName("Reflow"), "Layout",
     "getMarkerClassName() returns correct string when defined via string");
 
   TIMELINE_BLUEPRINT["fakemarker"] = { group: 0 };
   try {
     Utils.getMarkerClassName("fakemarker");
     ok(false, "getMarkerClassName() should throw when no label on blueprint.");
   } catch (e) {
     ok(true, "getMarkerClassName() should throw when no label on blueprint.");
   }
 
-  TIMELINE_BLUEPRINT["fakemarker"] = { group: 0, label: () => void 0};
+  TIMELINE_BLUEPRINT["fakemarker"] = { group: 0, label: () => void 0 };
   try {
     Utils.getMarkerClassName("fakemarker");
     ok(false, "getMarkerClassName() should throw when label function returnd undefined.");
   } catch (e) {
     ok(true, "getMarkerClassName() should throw when label function returnd undefined.");
   }
 
   delete TIMELINE_BLUEPRINT["fakemarker"];
--- a/browser/devtools/performance/views/optimizations-list.js
+++ b/browser/devtools/performance/views/optimizations-list.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
 const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
-const JIT_SAMPLES = L10N.getStr("jit.samples2");
+const JIT_SAMPLES = L10N.getStr("jit.samples");
 const JIT_EMPTY_TEXT = L10N.getStr("jit.empty");
 const PROPNAME_MAX_LENGTH = 4;
 
 /**
  * View for rendering a list of all optmizations found in a frame.
  * The terminology and types used here can be referenced:
  * @see browser/devtools/performance/modules/logic/jit.js
  */
--- a/browser/devtools/performance/views/recordings.js
+++ b/browser/devtools/performance/views/recordings.js
@@ -1,17 +1,13 @@
 /* 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 should be removed with bug 1163763.
-const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
-const DBG_L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
-
 /**
  * Functions handling the recordings UI.
  */
 let RecordingsView = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: function() {
@@ -146,17 +142,17 @@ let RecordingsView = Heritage.extend(Wid
    * @param RecordingModel recording
    *        The model of the recording that is being stopped.
    */
   _onRecordingWillStop: function(_, recording) {
     let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
 
     // Mark the corresponding item as loading.
     let durationNode = $(".recording-item-duration", recordingItem.target);
-    durationNode.setAttribute("value", DBG_L10N.getStr("loadingText"));
+    durationNode.setAttribute("value", L10N.getStr("recordingsList.loadingLabel"));
   },
 
   /**
    * Signals that a recording has been imported.
    *
    * @param RecordingModel model
    *        The recording model containing data on the recording session.
    */
@@ -209,18 +205,17 @@ let RecordingsView = Heritage.extend(Wid
     this.emit(EVENTS.RECORDING_SELECTED, model);
   }),
 
   /**
    * The click listener for the "save" button of each item in this container.
    */
   _onSaveButtonClick: function (e) {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-    // TODO localize? in bug 1163763
-    fp.init(window, "Save recording…", Ci.nsIFilePicker.modeSave);
+    fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
     fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
     fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
     fp.defaultString = "profile.json";
 
     fp.open({ done: result => {
       if (result == Ci.nsIFilePicker.returnCancel) {
         return;
       }
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -1020,23 +1020,27 @@ SwatchBasedEditorTooltip.prototype = {
    * tooltip knows about. That means from now on, clicking on that swatch will
    * toggle the editor.
    *
    * @param {node} swatchEl
    *        The element to add
    * @param {object} callbacks
    *        Callbacks that will be executed when the editor wants to preview a
    *        value change, or revert a change, or commit a change.
+   *        - onShow: will be called when one of the swatch tooltip is shown
    *        - onPreview: will be called when one of the sub-classes calls
    *        preview
    *        - onRevert: will be called when the user ESCapes out of the tooltip
    *        - onCommit: will be called when the user presses ENTER or clicks
    *        outside the tooltip.
    */
   addSwatch: function(swatchEl, callbacks={}) {
+    if (!callbacks.onShow) {
+      callbacks.onShow = function() {};
+    }
     if (!callbacks.onPreview) {
       callbacks.onPreview = function() {};
     }
     if (!callbacks.onRevert) {
       callbacks.onRevert = function() {};
     }
     if (!callbacks.onCommit) {
       callbacks.onCommit = function() {};
@@ -1064,16 +1068,17 @@ SwatchBasedEditorTooltip.prototype = {
 
     if (event.shiftKey) {
       event.stopPropagation();
       return;
     }
     if (swatch) {
       this.activeSwatch = event.target;
       this.show();
+      swatch.callbacks.onShow();
       event.stopPropagation();
     }
   },
 
   /**
    * Not called by this parent class, needs to be taken care of by sub-classes
    */
   preview: function(value) {
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -2844,16 +2844,19 @@ function TextPropertyEditor(aRuleEditor,
   this.browserWindow = this.doc.defaultView.top;
   this.removeOnRevert = this.prop.value === "";
 
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
+  this._onSwatchCommit = this._onSwatchCommit.bind(this);
+  this._onSwatchPreview = this._onSwatchPreview.bind(this);
+  this._onSwatchRevert = this._onSwatchRevert.bind(this);
   this._onValidate = throttle(this._previewValue, 10, this);
   this.update = this.update.bind(this);
 
   this._create();
   this.update();
 }
 
 TextPropertyEditor.prototype = {
@@ -3070,17 +3073,17 @@ TextPropertyEditor.prototype = {
       this.enable.setAttribute("checked", "");
     } else {
       this.enable.style.visibility = "visible";
       this.enable.removeAttribute("checked");
     }
 
     this.warning.hidden = this.editing || this.isValid();
 
-    if ((this.prop.overridden || !this.prop.enabled) && !this.editing) {
+    if (this.prop.overridden || !this.prop.enabled) {
       this.element.classList.add("ruleview-overridden");
     } else {
       this.element.classList.remove("ruleview-overridden");
     }
 
     let name = this.prop.name;
     this.nameSpan.textContent = name;
 
@@ -3125,48 +3128,51 @@ TextPropertyEditor.prototype = {
     // Attach the color picker tooltip to the color swatches
     this._colorSwatchSpans =
       this.valueSpan.querySelectorAll("." + colorSwatchClass);
     if (this.ruleEditor.isEditable) {
       for (let span of this._colorSwatchSpans) {
         // Adding this swatch to the list of swatches our colorpicker
         // knows about
         this.ruleView.tooltips.colorPicker.addSwatch(span, {
-          onPreview: () => this._previewValue(this.valueSpan.textContent),
-          onCommit: () => this._onValueDone(this.valueSpan.textContent, true),
-          onRevert: () => this._onValueDone(undefined, false)
+          onShow: this._onStartEditing,
+          onPreview: this._onSwatchPreview,
+          onCommit: this._onSwatchCommit,
+          onRevert: this._onSwatchRevert
         });
       }
     }
 
     // Attach the cubic-bezier tooltip to the bezier swatches
     this._bezierSwatchSpans =
       this.valueSpan.querySelectorAll("." + bezierSwatchClass);
     if (this.ruleEditor.isEditable) {
       for (let span of this._bezierSwatchSpans) {
         // Adding this swatch to the list of swatches our colorpicker
         // knows about
         this.ruleView.tooltips.cubicBezier.addSwatch(span, {
-          onPreview: () => this._previewValue(this.valueSpan.textContent),
-          onCommit: () => this._onValueDone(this.valueSpan.textContent, true),
-          onRevert: () => this._onValueDone(undefined, false)
+          onShow: this._onStartEditing,
+          onPreview: this._onSwatchPreview,
+          onCommit: this._onSwatchCommit,
+          onRevert: this._onSwatchRevert
         });
       }
     }
 
     // Attach the filter editor tooltip to the filter swatch
     let span = this.valueSpan.querySelector("." + filterSwatchClass);
     if (this.ruleEditor.isEditable) {
       if (span) {
         parserOptions.filterSwatch = true;
 
         this.ruleView.tooltips.filterEditor.addSwatch(span, {
-          onPreview: () => this._previewValue(this.valueSpan.textContent),
-          onCommit: () => this._onValueDone(this.valueSpan.textContent, true),
-          onRevert: () => this._onValueDone(undefined, false)
+          onShow: this._onStartEditing,
+          onPreview: this._onSwatchPreview,
+          onCommit: this._onSwatchCommit,
+          onRevert: this._onSwatchRevert
         }, outputParser, parserOptions);
       }
     }
 
     // Populate the computed styles.
     this._updateComputed();
 
     // Update the rule property highlight.
@@ -3418,16 +3424,40 @@ TextPropertyEditor.prototype = {
         if (!this.editing) {
           this.remove();
         }
       }, 0);
     }
   },
 
   /**
+   * Called when the swatch editor wants to commit a value change.
+   */
+  _onSwatchCommit: function() {
+    this._onValueDone(this.valueSpan.textContent, true);
+    this.update();
+  },
+
+  /**
+   * Called when the swatch editor wants to preview a value change.
+   */
+  _onSwatchPreview: function() {
+    this._previewValue(this.valueSpan.textContent);
+  },
+
+  /**
+   * Called when the swatch editor closes from an ESC. Revert to the original
+   * value of this property before editing.
+   */
+  _onSwatchRevert: function() {
+    this.rule.setPropertyEnabled(this.prop, this.prop.enabled);
+    this.update();
+  },
+
+  /**
    * Parse a value string and break it into pieces, starting with the
    * first value, and into an array of additional properties (if any).
    *
    * Example: Calling with "red; width: 100px" would return
    * { firstValue: "red", propertiesToAdd: [{ name: "width", value: "100px" }] }
    *
    * @param {string} aValue
    *        The string to parse
--- a/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-revert-on-ESC.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_colorpicker-revert-on-ESC.js
@@ -1,56 +1,121 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that a color change in the color picker is reverted when ESC is pressed
 
-const PAGE_CONTENT = [
-  '<style type="text/css">',
-  '  body {',
-  '    background-color: #ededed;',
-  '  }',
-  '</style>',
-  'Testing the color picker tooltip!'
+let TEST_URI = [
+  "<style type='text/css'>",
+  "  body {",
+  "    background-color: #EDEDED;",
+  "  }",
+  "</style>",
 ].join("\n");
 
 add_task(function*() {
-  yield addTab("data:text/html;charset=utf-8,rule view color picker tooltip test");
-  content.document.body.innerHTML = PAGE_CONTENT;
-  let {toolbox, inspector, view} = yield openRuleView();
-
-  let swatch = getRuleViewProperty(view, "body", "background-color").valueSpan
-    .querySelector(".ruleview-colorswatch");
-  yield testPressingEscapeRevertsChanges(swatch, view);
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {view} = yield openRuleView();
+  yield testPressingEscapeRevertsChanges(view);
+  yield testPressingEscapeRevertsChangesAndDisables(view);
 });
 
-function* testPressingEscapeRevertsChanges(swatch, ruleView) {
-  let cPicker = ruleView.tooltips.colorPicker;
+function* testPressingEscapeRevertsChanges(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
+  let cPicker = view.tooltips.colorPicker;
 
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
-  yield simulateColorPickerChange(ruleView, cPicker, [0, 0, 0, 1], {
+  yield simulateColorPickerChange(view, cPicker, [0, 0, 0, 1], {
     element: content.document.body,
     name: "backgroundColor",
     value: "rgb(0, 0, 0)"
   });
 
   is(swatch.style.backgroundColor, "rgb(0, 0, 0)",
     "The color swatch's background was updated");
-  is(getRuleViewProperty(ruleView, "body", "background-color").valueSpan.textContent,
-    "#000", "The text of the background-color css property was updated");
+  is(propEditor.valueSpan.textContent, "#000",
+    "The text of the background-color css property was updated");
 
   let spectrum = yield cPicker.spectrum;
 
-  // ESC out of the color picker
+  info("Pressing ESCAPE to close the tooltip");
   let onHidden = cPicker.tooltip.once("hidden");
   EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
+  yield ruleEditor.rule._applyingModifications;
 
-  yield waitForSuccess(() => {
-    return content.getComputedStyle(content.document.body).backgroundColor === "rgb(237, 237, 237)";
-  }, "The element's background-color was reverted");
+  yield waitForComputedStyleProperty("body", null, "background-color",
+    "rgb(237, 237, 237)");
+  is(propEditor.valueSpan.textContent, "#EDEDED",
+    "Got expected property value.");
 }
+
+function* testPressingEscapeRevertsChangesAndDisables(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
+  let cPicker = view.tooltips.colorPicker;
+
+  info("Disabling background-color property");
+  propEditor.enable.click();
+  yield ruleEditor.rule._applyingModifications;
+
+  ok(propEditor.element.classList.contains("ruleview-overridden"),
+    "property is overridden.");
+  is(propEditor.enable.style.visibility, "visible",
+    "property enable checkbox is visible.");
+  ok(!propEditor.enable.getAttribute("checked"),
+    "property enable checkbox is not checked.");
+  ok(!propEditor.prop.enabled,
+    "background-color property is disabled.");
+  let newValue = yield getRulePropertyValue("background-color");
+  is(newValue, "", "background-color should have been unset.");
+
+  let onShown = cPicker.tooltip.once("shown");
+  swatch.click();
+  yield onShown;
+
+  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+    "property overridden is not displayed.");
+  is(propEditor.enable.style.visibility, "hidden",
+    "property enable checkbox is hidden.");
+
+  let spectrum = yield cPicker.spectrum;
+  info("Simulating a color picker change in the widget");
+  spectrum.rgb = [0, 0, 0, 1];
+  yield ruleEditor.rule._applyingModifications;
+
+  info("Pressing ESCAPE to close the tooltip");
+  let onHidden = cPicker.tooltip.once("hidden");
+  EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
+  yield onHidden;
+  yield ruleEditor.rule._applyingModifications;
+
+  ok(propEditor.element.classList.contains("ruleview-overridden"),
+    "property is overridden.");
+  is(propEditor.enable.style.visibility, "visible",
+    "property enable checkbox is visible.");
+  ok(!propEditor.enable.getAttribute("checked"),
+    "property enable checkbox is not checked.");
+  ok(!propEditor.prop.enabled,
+    "background-color property is disabled.");
+  newValue = yield getRulePropertyValue("background-color");
+  is(newValue, "", "background-color should have been unset.");
+  is(propEditor.valueSpan.textContent, "#EDEDED",
+    "Got expected property value.");
+}
+
+function* getRulePropertyValue(name) {
+  let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+    styleSheetIndex: 0,
+    ruleIndex: 0,
+    name: name
+  });
+  return propValue;
+}
--- a/browser/devtools/styleinspector/test/browser_ruleview_cubicbezier-revert-on-ESC.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_cubicbezier-revert-on-ESC.js
@@ -1,53 +1,119 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test that changes made to the cubic-bezier timing-function in the cubic-bezier
-// tooltip are reverted when ESC is pressed
+// Test that changes made to the cubic-bezier timing-function in the
+// cubic-bezier tooltip are reverted when ESC is pressed
 
-const PAGE_CONTENT = [
-  '<style type="text/css">',
-  '  body {',
-  '    animation-timing-function: linear;',
-  '  }',
-  '</style>',
+let TEST_URI = [
+  "<style type='text/css'>",
+  "  body {",
+  "    animation-timing-function: linear;",
+  "  }",
+  "</style>",
 ].join("\n");
 
 add_task(function*() {
-  yield addTab("data:text/html;charset=utf-8,rule view cubic-bezier tooltip test");
-  content.document.body.innerHTML = PAGE_CONTENT;
-  let {toolbox, inspector, view} = yield openRuleView();
-
-  info("Getting the bezier swatch element");
-  let swatch = getRuleViewProperty(view, "body", "animation-timing-function").valueSpan
-    .querySelector(".ruleview-bezierswatch");
-  yield testPressingEscapeRevertsChanges(swatch, view);
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {view} = yield openRuleView();
+  yield testPressingEscapeRevertsChanges(view);
+  yield testPressingEscapeRevertsChangesAndDisables(view);
 });
 
-function* testPressingEscapeRevertsChanges(swatch, ruleView) {
-  let bezierTooltip = ruleView.tooltips.cubicBezier;
+function* testPressingEscapeRevertsChanges(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
+  let bezierTooltip = view.tooltips.cubicBezier;
 
   let onShown = bezierTooltip.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   let widget = yield bezierTooltip.widget;
   info("Simulating a change of curve in the widget");
   widget.coordinates = [0.1, 2, 0.9, -1];
-  let expected = "cubic-bezier(0.1, 2, 0.9, -1)";
+  yield ruleEditor.rule._applyingModifications;
 
-  yield waitForSuccess(() => {
-    return content.getComputedStyle(content.document.body).animationTimingFunction === expected;
-  }, "Waiting for the change to be previewed on the element");
+  yield waitForComputedStyleProperty("body", null, "animation-timing-function",
+    "cubic-bezier(0.1, 2, 0.9, -1)");
+  is(propEditor.valueSpan.textContent, "cubic-bezier(.1,2,.9,-1)",
+    "Got expected property value.");
 
   info("Pressing ESCAPE to close the tooltip");
   let onHidden = bezierTooltip.tooltip.once("hidden");
   EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
   yield onHidden;
+  yield ruleEditor.rule._applyingModifications;
 
-  yield waitForSuccess(() => {
-    return content.getComputedStyle(content.document.body).animationTimingFunction === "cubic-bezier(0, 0, 1, 1)";
-  }, "Waiting for the change to be reverted on the element");
+  yield waitForComputedStyleProperty("body", null, "animation-timing-function",
+    "cubic-bezier(0, 0, 1, 1)");
+  is(propEditor.valueSpan.textContent, "linear",
+    "Got expected property value.");
 }
+
+function* testPressingEscapeRevertsChangesAndDisables(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
+  let bezierTooltip = view.tooltips.cubicBezier;
+
+  info("Disabling animation-timing-function property");
+  propEditor.enable.click();
+  yield ruleEditor.rule._applyingModifications;
+
+  ok(propEditor.element.classList.contains("ruleview-overridden"),
+    "property is overridden.");
+  is(propEditor.enable.style.visibility, "visible",
+    "property enable checkbox is visible.");
+  ok(!propEditor.enable.getAttribute("checked"),
+    "property enable checkbox is not checked.");
+  ok(!propEditor.prop.enabled,
+    "animation-timing-function property is disabled.");
+  let newValue = yield getRulePropertyValue("animation-timing-function");
+  is(newValue, "", "animation-timing-function should have been unset.");
+
+  let onShown = bezierTooltip.tooltip.once("shown");
+  swatch.click();
+  yield onShown;
+
+  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+    "property overridden is not displayed.");
+  is(propEditor.enable.style.visibility, "hidden",
+    "property enable checkbox is hidden.");
+
+  let widget = yield bezierTooltip.widget;
+  info("Simulating a change of curve in the widget");
+  widget.coordinates = [0.1, 2, 0.9, -1];
+  yield ruleEditor.rule._applyingModifications;
+
+  info("Pressing ESCAPE to close the tooltip");
+  let onHidden = bezierTooltip.tooltip.once("hidden");
+  EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
+  yield onHidden;
+  yield ruleEditor.rule._applyingModifications;
+
+  ok(propEditor.element.classList.contains("ruleview-overridden"),
+    "property is overridden.");
+  is(propEditor.enable.style.visibility, "visible",
+    "property enable checkbox is visible.");
+  ok(!propEditor.enable.getAttribute("checked"),
+    "property enable checkbox is not checked.");
+  ok(!propEditor.prop.enabled,
+    "animation-timing-function property is disabled.");
+  newValue = yield getRulePropertyValue("animation-timing-function");
+  is(newValue, "", "animation-timing-function should have been unset.");
+  is(propEditor.valueSpan.textContent, "linear",
+    "Got expected property value.");
+}
+
+function* getRulePropertyValue(name) {
+  let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+    styleSheetIndex: 0,
+    ruleIndex: 0,
+    name: name
+  });
+  return propValue;
+}
--- a/browser/devtools/styleinspector/test/browser_ruleview_filtereditor-revert-on-ESC.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_filtereditor-revert-on-ESC.js
@@ -1,38 +1,104 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Tests the Filter Editor Tooltip reverting changes on ESC
+// Tests that changes made to the Filter Editor Tooltip are reverted when
+// ESC is pressed
 
 const TEST_URL = TEST_URL_ROOT + "doc_filter.html";
 
 add_task(function*() {
   yield addTab(TEST_URL);
-
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {view} = yield openRuleView();
+  yield testPressingEscapeRevertsChanges(view);
+  yield testPressingEscapeRevertsChangesAndDisables(view);
+});
 
-  info("Getting the filter swatch element");
-  let swatch = getRuleViewProperty(view, "body", "filter").valueSpan
-    .querySelector(".ruleview-filterswatch");
+function* testPressingEscapeRevertsChanges(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch");
+  let filterTooltip = view.tooltips.filterEditor;
 
-  let filterTooltip = view.tooltips.filterEditor;
   let onShow = filterTooltip.tooltip.once("shown");
   swatch.click();
   yield onShow;
 
   let widget = yield filterTooltip.widget;
+  widget.setCssValue("blur(2px)");
+  yield ruleEditor.rule._applyingModifications;
 
-  widget.setCssValue("blur(2px)");
   yield waitForComputedStyleProperty("body", null, "filter", "blur(2px)");
-
-  ok(true, "Changes previewed on the element");
+  is(propEditor.valueSpan.textContent, "blur(2px)",
+    "Got expected property value.");
 
   info("Pressing ESCAPE to close the tooltip");
   EventUtils.sendKey("ESCAPE", widget.styleWindow);
+  yield ruleEditor.rule._applyingModifications;
 
-  yield waitForSuccess(() => {
-    const computed = content.getComputedStyle(content.document.body);
-    return computed.filter === "blur(2px) contrast(2)";
-  }, "Waiting for the change to be reverted on the element");
-});
+  yield waitForComputedStyleProperty("body", null, "filter",
+    "blur(2px) contrast(2)");
+  is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)",
+    "Got expected property value.");
+}
+
+function* testPressingEscapeRevertsChangesAndDisables(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-filterswatch");
+  let filterTooltip = view.tooltips.filterEditor;
+
+  info("Disabling filter property");
+  propEditor.enable.click();
+  yield ruleEditor.rule._applyingModifications;
+
+  ok(propEditor.element.classList.contains("ruleview-overridden"),
+    "property is overridden.");
+  is(propEditor.enable.style.visibility, "visible",
+    "property enable checkbox is visible.");
+  ok(!propEditor.enable.getAttribute("checked"),
+    "property enable checkbox is not checked.");
+  ok(!propEditor.prop.enabled,
+    "filter property is disabled.");
+  let newValue = yield getRulePropertyValue("filter");
+  is(newValue, "", "filter should have been unset.");
+
+  let onShow = filterTooltip.tooltip.once("shown");
+  swatch.click();
+  yield onShow;
+
+  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+    "property overridden is not displayed.");
+  is(propEditor.enable.style.visibility, "hidden",
+    "property enable checkbox is hidden.");
+
+  let widget = yield filterTooltip.widget;
+  widget.setCssValue("blur(2px)");
+  yield ruleEditor.rule._applyingModifications;
+
+  info("Pressing ESCAPE to close the tooltip");
+  EventUtils.sendKey("ESCAPE", widget.styleWindow);
+  yield ruleEditor.rule._applyingModifications;
+
+  ok(propEditor.element.classList.contains("ruleview-overridden"),
+    "property is overridden.");
+  is(propEditor.enable.style.visibility, "visible",
+    "property enable checkbox is visible.");
+  ok(!propEditor.enable.getAttribute("checked"),
+    "property enable checkbox is not checked.");
+  ok(!propEditor.prop.enabled, "filter property is disabled.");
+  newValue = yield getRulePropertyValue("filter");
+  is(newValue, "", "filter should have been unset.");
+  is(propEditor.valueSpan.textContent, "blur(2px) contrast(2)",
+    "Got expected property value.");
+}
+
+function* getRulePropertyValue(name) {
+  let propValue = yield executeInContent("Test:GetRulePropertyValue", {
+    styleSheetIndex: 0,
+    ruleIndex: 0,
+    name: name
+  });
+  return propValue;
+}
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/markers.properties
@@ -0,0 +1,80 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used inside the Performance Tools
+# which is available from the Web Developer sub-menu -> 'Performance'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# 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. These strings
+# are specifically for marker names in the performance tool.
+
+# LOCALIZATION NOTE (marker.label.*):
+# These strings are displayed in the Performance Tool waterfall, identifying markers.
+# We want to use the same wording as Google Chrome when appropriate.
+marker.label.styles=Recalculate Style
+marker.label.reflow=Layout
+marker.label.paint=Paint
+marker.label.javascript=Function Call
+marker.label.parseHTML=Parse HTML
+marker.label.parseXML=Parse XML
+marker.label.domevent=DOM Event
+marker.label.consoleTime=Console
+marker.label.garbageCollection=Incremental GC
+marker.label.garbageCollection.nonIncremental=Non-incremental GC
+marker.label.cycleCollection=Cycle Collection
+marker.label.cycleCollection.forgetSkippable=CC Graph Reduction
+marker.label.timestamp=Timestamp
+marker.label.unknown=Unknown
+
+# LOCALIZATION NOTE (marker.label.javascript.*):
+# These strings are displayed as JavaScript markers that have special
+# reasons that can be translated.
+marker.label.javascript.scriptElement=Script Tag
+marker.label.javascript.promiseCallback=Promise Callback
+marker.label.javascript.promiseInit=Promise Init
+marker.label.javascript.workerRunnable=Worker
+marker.label.javascript.jsURI=JavaScript URI
+marker.label.javascript.eventHandler=Event Handler
+
+# LOCALIZATION NOTE (marker.fieldFormat):
+# Some timeline markers come with details, like a size, a name, a js function.
+# %1$S is replaced with one of the above label (marker.label.*) and %2$S
+# with the details. For examples: Paint (200x100), or console.time (FOO)
+marker.fieldFormat=%1$S (%2$S)
+
+# LOCALIZATION NOTE (marker.field.*):
+# Strings used in the waterfall sidebar as property names.
+
+# General marker fields
+marker.field.start=Start:
+marker.field.end=End:
+marker.field.duration=Duration:
+# Field names for stack values
+marker.field.stack=Stack:
+marker.field.startStack=Stack at start:
+marker.field.endStack=Stack at end:
+# %S is the "Async Cause" of a marker, and this signifies that the cause
+# was an asynchronous one in a displayed stack.
+marker.field.asyncStack=(Async: %S)
+# For console.time markers
+marker.field.consoleTimerName=Timer Name:
+# For DOM Event markers
+marker.field.DOMEventType=Event Type:
+marker.field.DOMEventPhase=Phase:
+# Non-incremental cause for a Garbage Collection marker
+marker.field.nonIncrementalCause=Non-incremental Cause:
+# For "Recalculate Style" markers
+marker.field.restyleHint=Restyle Hint:
+# General "reason" for a marker (JavaScript, Garbage Collection)
+marker.field.causeName=Cause:
+# General "type" for a marker (Cycle Collection, Garbage Collection)
+marker.field.type=Type:
+
+# Strings used in the waterfall sidebar as values.
+marker.value.unknownFrame=<unknown location>
+marker.value.DOMEventTargetPhase=Target
+marker.value.DOMEventCapturingPhase=Capture
+marker.value.DOMEventBubblingPhase=Bubbling
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/performance.dtd
@@ -0,0 +1,157 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the Performance strings -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
+
+<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
+  - keep it in English, or another language commonly spoken among web developers.
+  - 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 (performanceUI.startRecording/performanceUI.stopRecording): These are
+  -  the labels shown on the main recording buttons to start/stop recording. -->
+<!ENTITY performanceUI.startRecording "Start Recording Performance">
+<!ENTITY performanceUI.stopRecording  "Stop Recording Performance">
+
+<!-- LOCALIZATION NOTE (performanceUI.bufferStatusTooltip): This string
+  -  is displayed as the tooltip for the buffer capacity during a recording. -->
+<!ENTITY performanceUI.bufferStatusTooltip "The profiler stores samples in a circular buffer, and once the buffer reaches the limit for a recording, newer samples begin to overwrite samples at the beginning of the recording.">
+
+<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.nonE10SBuild): This string
+  -  is displayed as a message for why the real time overview graph is disabled
+  -  when running on a non-multiprocess build. -->
+<!ENTITY performanceUI.disabledRealTime.nonE10SBuild "Realtime recording data disabled on non-multiprocess Firefox.">
+
+<!-- LOCALIZATION NOTE (performanceUI.disabledRealTime.disabledE10S): This string
+  -  is displayed as a message for why the real time overview graph is disabled
+  -  when running on a build that can run multiprocess Firefox, but just is not enabled. -->
+<!ENTITY performanceUI.disabledRealTime.disabledE10S "Enable multiprocess Firefox in preferences for rendering recording data in realtime.">
+
+<!-- LOCALIZATION NOTE (performanceUI.bufferStatusFull): This string
+  -  is displayed when the profiler's circular buffer has started to overlap. -->
+<!ENTITY performanceUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten.">
+
+<!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown
+  -  in the call list view while loading a profile. -->
+<!ENTITY performanceUI.loadingNotice "Loading…">
+
+<!-- LOCALIZATION NOTE (performanceUI.recordButton): This string is displayed
+  -  on a button that starts a new profile. -->
+<!ENTITY performanceUI.recordButton.tooltip "Toggle the recording state of a performance recording.">
+
+<!-- LOCALIZATION NOTE (performanceUI.importButton): This string is displayed
+  -  on a button that opens a dialog to import a saved profile data file. -->
+<!ENTITY performanceUI.importButton "Import…">
+
+<!-- LOCALIZATION NOTE (performanceUI.exportButton): This string is displayed
+  -  on a button that opens a dialog to export a saved profile data file. -->
+<!ENTITY performanceUI.exportButton "Save">
+
+<!-- LOCALIZATION NOTE (performanceUI.clearButton): This string is displayed
+  -  on a button that remvoes all the recordings. -->
+<!ENTITY performanceUI.clearButton "Clear">
+
+<!-- LOCALIZATION NOTE (performanceUI.toolbar.*): These strings are displayed
+  -  in the toolbar on buttons that select which view is currently shown. -->
+<!ENTITY performanceUI.toolbar.waterfall "Waterfall">
+<!ENTITY performanceUI.toolbar.js-calltree "Call Tree">
+<!ENTITY performanceUI.toolbar.memory-calltree "Allocations">
+<!ENTITY performanceUI.toolbar.js-flamegraph "JS Flame Chart">
+<!ENTITY performanceUI.toolbar.memory-flamegraph "Allocations Flame Chart">
+
+<!-- LOCALIZATION NOTE (performanceUI.table.*): These strings are displayed
+  -  in the call tree headers for a recording. -->
+<!ENTITY performanceUI.table.totalDuration            "Total Time">
+<!ENTITY performanceUI.table.totalDuration.tooltip    "The amount of time spent in this function and functions it calls.">
+<!ENTITY performanceUI.table.selfDuration             "Self Time">
+<!ENTITY performanceUI.table.selfDuration.tooltip     "The amount of time spent only within this function.">
+<!ENTITY performanceUI.table.totalPercentage          "Total Cost">
+<!ENTITY performanceUI.table.totalPercentage.tooltip  "The percentage of time spent in this function and functions it calls.">
+<!ENTITY performanceUI.table.selfPercentage           "Self Cost">
+<!ENTITY performanceUI.table.selfPercentage.tooltip   "The percentage of time spent only within this function.">
+<!ENTITY performanceUI.table.samples                  "Samples">
+<!ENTITY performanceUI.table.samples.tooltip          "The number of times this function was on the stack when the profiler took a sample.">
+<!ENTITY performanceUI.table.function                 "Function">
+<!ENTITY performanceUI.table.function.tooltip         "The name and source location of the sampled function.">
+<!ENTITY performanceUI.table.totalAlloc               "Total Sampled Allocations">
+<!ENTITY performanceUI.table.totalAlloc.tooltip       "The total number of Object allocations sampled at this location and in callees.">
+<!ENTITY performanceUI.table.selfAlloc                "Self Sampled Allocations">
+<!ENTITY performanceUI.table.selfAlloc.tooltip        "The number of Object allocations sampled at this location.">
+
+<!-- LOCALIZATION NOTE (performanceUI.newtab.tooltiptext): The tooltiptext shown
+  -  on the "+" (new tab) button for a profile when a selection is available. -->
+<!ENTITY performanceUI.newtab.tooltiptext "Add new tab from selection">
+
+<!-- LOCALIZATION NOTE (performanceUI.toolbar.filter.tooltiptext): This string
+  -  is displayed next to the filter button-->
+<!ENTITY performanceUI.options.filter.tooltiptext "Select what data to display in the timeline">
+
+<!-- LOCALIZATION NOTE (performanceUI.options.tooltiptext): This is the tooltip
+  -  for the options button. -->
+<!ENTITY performanceUI.options.gear.tooltiptext "Configure performance preferences.">
+
+<!-- LOCALIZATION NOTE (performanceUI.invertTree): This is the label shown next to
+  -  a checkbox that inverts and un-inverts the profiler's call tree. -->
+<!ENTITY performanceUI.invertTree             "Invert Call Tree">
+<!ENTITY performanceUI.invertTree.tooltiptext "Inverting the call tree displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
+
+<!-- LOCALIZATION NOTE (performanceUI.invertFlameGraph): This is the label shown next to
+  -  a checkbox that inverts and un-inverts the profiler's flame graph. -->
+<!ENTITY performanceUI.invertFlameGraph             "Invert Flame Chart">
+<!ENTITY performanceUI.invertFlameGraph.tooltiptext "Inverting the flame chart displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
+
+<!-- LOCALIZATION NOTE (performanceUI.showPlatformData): This is the
+  -  label for the checkbox that toggles whether or not Gecko platform data
+  -  is displayed in the profiler. -->
+<!ENTITY performanceUI.showPlatformData             "Show Gecko Platform Data">
+<!ENTITY performanceUI.showPlatformData.tooltiptext "Showing platform data enables the JavaScript Profiler reports to include Gecko platform symbols.">
+
+<!-- LOCALIZATION NOTE (performanceUI.flattenTreeRecursion): This is the
+  -  label for the checkbox that toggles the flattening of tree recursion in inspected
+  -  functions in the profiler. -->
+<!ENTITY performanceUI.flattenTreeRecursion             "Flatten Tree Recursion">
+<!ENTITY performanceUI.flattenTreeRecursion.tooltiptext "Flatten recursion when inspecting functions.">
+
+<!-- LOCALIZATION NOTE (performanceUI.enableMemory): This string
+  -  is displayed next to a checkbox determining whether or not memory
+  -  measurements are enabled. -->
+<!ENTITY performanceUI.enableMemory             "Record Memory">
+<!ENTITY performanceUI.enableMemory.tooltiptext "Record memory consumption while profiling.">
+
+<!-- LOCALIZATION NOTE (performanceUI.enableAllocations): This string
+  -  is displayed next to a checkbox determining whether or not allocation
+  -  measurements are enabled. -->
+<!ENTITY performanceUI.enableAllocations             "Record Allocations">
+<!ENTITY performanceUI.enableAllocations.tooltiptext "Record Object allocations while profiling.">
+
+<!-- LOCALIZATION NOTE (performanceUI.enableFramerate): This string
+  -  is displayed next to a checkbox determining whether or not framerate
+  -  is recorded. -->
+<!ENTITY performanceUI.enableFramerate             "Record Framerate">
+<!ENTITY performanceUI.enableFramerate.tooltiptext "Record framerate while profiling.">
+
+<!-- LOCALIZATION NOTE (performanceUI.enableJITOptimizations): This string
+  -  is displayed next to a checkbox determining whether or not JIT optimization data
+  -  should be recorded. -->
+<!ENTITY performanceUI.enableJITOptimizations             "Record JIT Optimizations">
+<!ENTITY performanceUI.enableJITOptimizations.tooltiptext "Record JIT optimization data sampled in each JavaScript frame.">
+
+<!-- LOCALIZATION NOTE (performanceUI.JITOptimizationsTitle): This string
+  -  is displayed as the title of the JIT Optimizations panel. -->
+<!ENTITY performanceUI.JITOptimizationsTitle "JIT Optimizations">
+
+<!-- LOCALIZATION NOTE (performanceUI.console.recordingNoticeStart/recordingNoticeEnd):
+  -  This string is displayed when a recording is selected that started via console.profile.
+  -  Wraps the command used to start, like "Currently recording via console.profile("label")" -->
+<!ENTITY performanceUI.console.recordingNoticeStart "Currently recording via">
+<!ENTITY performanceUI.console.recordingNoticeEnd   "">
+
+<!-- LOCALIZATION NOTE (performanceUI.console.stopCommandStart/stopCommandEnd):
+  -  This string is displayed when a recording is selected that started via console.profile.
+  -  Indicates how to stop the recording, wrapping the command, like
+  -  "Stop recording by entering console.profileEnd("label") into the console." -->
+<!ENTITY performanceUI.console.stopCommandStart "Stop recording by entering">
+<!ENTITY performanceUI.console.stopCommandEnd   "into the console.">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/performance.properties
@@ -0,0 +1,172 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used inside the Performance Tools
+# which is available from the Web Developer sub-menu -> 'Performance'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# 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 (performance.label):
+# This string is displayed in the title of the tab when the profiler is
+# displayed inside the developer tools window and in the Developer Tools Menu.
+performance.label=Performance
+
+# LOCALIZATION NOTE (performance.panelLabel):
+# This is used as the label for the toolbox panel.
+performance.panelLabel=Performance Panel
+
+# LOCALIZATION NOTE (performance.commandkey, performance.accesskey)
+# Used for the menuitem in the tool menu
+performance.commandkey=VK_F5
+performance.accesskey=P
+
+# LOCALIZATION NOTE (performance.tooltip):
+# This string is displayed in the tooltip of the tab when the profiler is
+# displayed inside the developer tools window.
+# Keyboard shortcut for Performance Tools will be shown inside brackets.
+performance.tooltip=Performance (%S)
+
+# LOCALIZATION NOTE (noRecordingsText): The text to display in the
+# recordings menu when there are no recorded profiles yet.
+noRecordingsText=There are no profiles yet.
+
+# LOCALIZATION NOTE (recordingsList.itemLabel):
+# This string is displayed in the recordings list of the Performance Tools,
+# identifying a set of function calls.
+recordingsList.itemLabel=Recording #%S
+
+# LOCALIZATION NOTE (recordingsList.recordingLabel):
+# This string is displayed in the recordings list of the Performance Tools,
+# for an item that has not finished recording.
+recordingsList.recordingLabel=In progress…
+
+# LOCALIZATION NOTE (recordingsList.loadingLabel):
+# This string is displayed in the recordings list of the Performance Tools,
+# for an item that is finished and is loading.
+recordingsList.loadingLabel=Loading…
+
+# LOCALIZATION NOTE (recordingsList.durationLabel):
+# This string is displayed in the recordings list of the Performance Tools,
+# for an item that has finished recording.
+recordingsList.durationLabel=%S ms
+
+# LOCALIZATION NOTE (recordingsList.saveLabel):
+# This string is displayed in the recordings list of the Performance Tools,
+# for saving an item to disk.
+recordingsList.saveLabel=Save
+
+# LOCALIZATION NOTE (graphs.fps):
+# This string is displayed in the framerate graph of the Performance Tools,
+# as the unit used to measure frames per second. This label should be kept
+# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
+graphs.fps=fps
+
+# LOCALIZATION NOTE (graphs.ms):
+# This string is displayed in the flamegraph of the Performance Tools,
+# as the unit used to measure time (in milliseconds). This label should be kept
+# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
+graphs.ms=ms
+
+# 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 (category.*):
+# These strings are displayed in the categories graph of the Performance Tools,
+# as the legend for each block in every bar. These labels should be kept
+# AS SHORT AS POSSIBLE so they don't obstruct important parts of the graph.
+category.other=Gecko
+category.css=Styles
+category.js=JIT
+category.gc=GC
+category.network=Network
+category.graphics=Graphics
+category.storage=Storage
+category.events=Input & Events
+category.tools=Tools
+
+# LOCALIZATION NOTE (table.ms):
+# This string is displayed in the call tree after units of time in milliseconds.
+table.ms=ms
+
+# LOCALIZATION NOTE (table.percentage):
+# This string is displayed in the call tree after units representing percentages.
+table.percentage=%
+
+# LOCALIZATION NOTE (table.root):
+# This string is displayed in the call tree for the root node.
+table.root=(root)
+
+# LOCALIZATION NOTE (table.idle):
+# This string is displayed in the call tree for the idle blocks.
+table.idle=(idle)
+
+# LOCALIZATION NOTE (table.url.tooltiptext):
+# This string is displayed in the call tree as the tooltip text for the url
+# labels which, when clicked, jump to the debugger.
+table.url.tooltiptext=View source in Debugger
+
+# LOCALIZATION NOTE (table.zoom.tooltiptext):
+# This string is displayed in the call tree as the tooltip text for the 'zoom'
+# buttons (small magnifying glass icons) which spawn a new tab.
+table.zoom.tooltiptext=Inspect frame in new tab
+
+# LOCALIZATION NOTE (table.view-optimizations.tooltiptext):
+# This string is displayed in the icon displayed next to frames that
+# have optimization data
+table.view-optimizations.tooltiptext=View optimizations in JIT View
+
+# LOCALIZATION NOTE (recordingsList.importDialogTitle):
+# This string is displayed as a title for importing a recoring from disk.
+recordingsList.importDialogTitle=Import recording…
+
+# LOCALIZATION NOTE (recordingsList.saveDialogTitle):
+# This string is displayed as a title for saving a recording to disk.
+recordingsList.saveDialogTitle=Save recording…
+
+# LOCALIZATION NOTE (recordingsList.saveDialogJSONFilter):
+# This string is displayed as a filter for saving a recording to disk.
+recordingsList.saveDialogJSONFilter=JSON Files
+
+# LOCALIZATION NOTE (recordingsList.saveDialogAllFilter):
+# This string is displayed as a filter for saving a recording to disk.
+recordingsList.saveDialogAllFilter=All Files
+
+# LOCALIZATION NOTE (jit.optimizationFailure):
+# This string is displayed in a tooltip when no JIT optimizations were detected.
+jit.optimizationFailure=Optimization failed
+
+# LOCALIZATION NOTE (jit.samples):
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# This string is displayed for the unit representing the number of times a
+# frame is sampled.
+# "#1" represents the number of samples
+# example: 30 samples
+jit.samples=#1 sample;#1 samples
+
+# LOCALIZATION NOTE (jit.empty):
+# This string is displayed when there are no JIT optimizations to display.
+jit.empty=No JIT optimizations recorded for this frame.
+
+# LOCALIZATION NOTE (timeline.tick):
+# This string is displayed in the timeline overview, for delimiting ticks
+# by time, in milliseconds.
+timeline.tick=%S ms
+
+# LOCALIZATION NOTE (timeline.records):
+# This string is displayed in the timeline waterfall, as a title for the menu.
+timeline.records=RECORDS
+
+# LOCALIZATION NOTE (profiler.bufferFull):
+# This string is displayed when recording, indicating how much of the
+# buffer is currently be used.
+# %S is the percentage of the buffer used -- there are two "%"s after to escape
+# the % that is actually displayed.
+# Example: "Buffer 54% full"
+profiler.bufferFull=Buffer %S%% full
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.dtd
+++ /dev/null
@@ -1,166 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- LOCALIZATION NOTE : FILE This file contains the Profiler strings -->
-<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
-
-<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
-  - keep it in English, or another language commonly spoken among web developers.
-  - 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 (profilerUI.emptyNotice1/2): This is the label shown
-  -  in the call list view when empty. -->
-<!-- TODO remove -->
-<!ENTITY profilerUI.emptyNotice1    "Click on the">
-<!-- TODO remove -->
-<!ENTITY profilerUI.emptyNotice2    "button to start recording JavaScript function calls.">
-
-<!-- LOCALIZATION NOTE (profilerUI.stopNotice1/2): This is the label shown
-  -  in the call list view while recording a profile. -->
-<!-- TODO remove -->
-<!ENTITY profilerUI.stopNotice1    "Click on the">
-<!-- TODO remove -->
-<!ENTITY profilerUI.stopNotice2    "button again to stop profiling.">
-
-<!-- LOCALIZATION NOTE (profilerUI.startRecording/profilerUI.stopRecording): These are
-  -  the labels shown on the main recording buttons to start/stop recording. -->
-<!ENTITY profilerUI.startRecording "Start Recording Performance">
-<!ENTITY profilerUI.stopRecording  "Stop Recording Performance">
-
-<!-- LOCALIZATION NOTE (profilerUI.bufferStatusTooltip): This string
-  -  is displayed as the tooltip for the buffer capacity during a recording. -->
-<!ENTITY profilerUI.bufferStatusTooltip "The profiler stores samples in a circular buffer, and once the buffer reaches the limit for a recording, newer samples begin to overwrite samples at the beginning of the recording.">
-
-<!-- LOCALIZATION NOTE (profilerUI.bufferStatusFull): This string
-  -  is displayed when the profiler's circular buffer has started to overlap. -->
-<!ENTITY profilerUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten.">
-
-<!-- LOCALIZATION NOTE (profilerUI.loadingNotice): This is the label shown
-  -  in the call list view while loading a profile. -->
-<!ENTITY profilerUI.loadingNotice "Loading…">
-
-<!-- LOCALIZATION NOTE (profilerUI.recordButton): This string is displayed
-  -  on a button that starts a new profile. -->
-<!-- TODO remove -->
-<!ENTITY profilerUI.recordButton.tooltip "Record JavaScript function calls.">
-
-<!-- LOCALIZATION NOTE (profilerUI.recordButton2): This string is displayed
-  -  on a button that starts a new profile. -->
-<!ENTITY profilerUI.recordButton2.tooltip "Toggle the recording state of a performance recording.">
-
-<!-- LOCALIZATION NOTE (profilerUI.importButton): This string is displayed
-  -  on a button that opens a dialog to import a saved profile data file. -->
-<!ENTITY profilerUI.importButton "Import…">
-
-<!-- LOCALIZATION NOTE (profilerUI.exportButton): This string is displayed
-  -  on a button that opens a dialog to export a saved profile data file. -->
-<!ENTITY profilerUI.exportButton "Save">
-
-<!-- LOCALIZATION NOTE (profilerUI.clearButton): This string is displayed
-  -  on a button that remvoes all the recordings. -->
-<!ENTITY profilerUI.clearButton "Clear">
-
-<!-- LOCALIZATION NOTE (profilerUI.toolbar.*): These strings are displayed
-  -  in the toolbar on buttons that select which view is currently shown. -->
-<!ENTITY profilerUI.toolbar.waterfall "Timeline">
-<!ENTITY profilerUI.toolbar.js-calltree "JavaScript">
-<!ENTITY profilerUI.toolbar.memory-calltree1 "Allocations">
-<!ENTITY profilerUI.toolbar.js-flamegraph "JS Flame Chart">
-<!ENTITY profilerUI.toolbar.memory-flamegraph1 "Allocations Flame Chart">
-
-<!-- LOCALIZATION NOTE (profilerUI.table.*): These strings are displayed
-  -  in the call tree headers for a recording. -->
-<!ENTITY profilerUI.table.totalDuration2           "Total Time">
-<!ENTITY profilerUI.table.totalDuration.tooltip    "The amount of time spent in this function and functions it calls.">
-<!ENTITY profilerUI.table.selfDuration2            "Self Time">
-<!ENTITY profilerUI.table.selfDuration.tooltip     "The amount of time spent only within this function.">
-<!ENTITY profilerUI.table.totalPercentage          "Total Cost">
-<!ENTITY profilerUI.table.totalPercentage.tooltip  "The percentage of time spent in this function and functions it calls.">
-<!ENTITY profilerUI.table.selfPercentage           "Self Cost">
-<!ENTITY profilerUI.table.selfPercentage.tooltip   "The percentage of time spent only within this function.">
-<!ENTITY profilerUI.table.samples                  "Samples">
-<!ENTITY profilerUI.table.samples.tooltip          "The number of times this function was on the stack when the profiler took a sample.">
-<!ENTITY profilerUI.table.function                 "Function">
-<!ENTITY profilerUI.table.function.tooltip         "The name and source location of the sampled function.">
-<!ENTITY profilerUI.table.totalAlloc1              "Total Sampled Allocations">
-<!ENTITY profilerUI.table.totalAlloc.tooltip       "The total number of Object allocations sampled at this location and in callees.">
-<!ENTITY profilerUI.table.selfAlloc1               "Self Sampled Allocations">
-<!ENTITY profilerUI.table.selfAlloc.tooltip        "The number of Object allocations sampled at this location.">
-
-<!-- LOCALIZATION NOTE (profilerUI.newtab.tooltiptext): The tooltiptext shown
-  -  on the "+" (new tab) button for a profile when a selection is available. -->
-<!ENTITY profilerUI.newtab.tooltiptext "Add new tab from selection">
-
-<!-- LOCALIZATION NOTE (profilerUI.toolbar.filter.tooltiptext): This string
-  -  is displayed next to the filter button-->
-<!ENTITY profilerUI.options.filter.tooltiptext "Select what data to display in the timeline">
-
-<!-- LOCALIZATION NOTE (profilerUI.options.tooltiptext): This is the tooltip
-  -  for the options button. -->
-<!ENTITY profilerUI.options.gear.tooltiptext "Configure performance preferences.">
-
-<!-- LOCALIZATION NOTE (profilerUI.invertTree): This is the label shown next to
-  -  a checkbox that inverts and un-inverts the profiler's call tree. -->
-<!ENTITY profilerUI.invertTree             "Invert Call Tree">
-<!ENTITY profilerUI.invertTree.tooltiptext "Inverting the call tree displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
-
-<!-- LOCALIZATION NOTE (profilerUI.invertFlameGraph): This is the label shown next to
-  -  a checkbox that inverts and un-inverts the profiler's flame graph. -->
-<!ENTITY profilerUI.invertFlameGraph             "Invert Flame Chart">
-<!ENTITY profilerUI.invertFlameGraph.tooltiptext "Inverting the flame chart displays the profiled call paths starting from the youngest frames and expanding out to the older frames.">
-
-<!-- LOCALIZATION NOTE (profilerUI.showPlatformData): This is the
-  -  label for the checkbox that toggles whether or not Gecko platform data
-  -  is displayed in the profiler. -->
-<!ENTITY profilerUI.showPlatformData             "Show Gecko Platform Data">
-<!ENTITY profilerUI.showPlatformData.tooltiptext "Showing platform data enables the JavaScript Profiler reports to include Gecko platform symbols.">
-
-<!-- LOCALIZATION NOTE (profilerUI.flattenTreeRecursion): This is the
-  -  label for the checkbox that toggles the flattening of tree recursion in inspected
-  -  functions in the profiler. -->
-<!ENTITY profilerUI.flattenTreeRecursion             "Flatten Tree Recursion">
-<!ENTITY profilerUI.flattenTreeRecursion.tooltiptext "Flatten recursion when inspecting functions.">
-
-<!-- LOCALIZATION NOTE (profilerUI.enableMemory): This string
-  -  is displayed next to a checkbox determining whether or not memory
-  -  measurements are enabled. -->
-<!ENTITY profilerUI.enableMemory             "Record Memory">
-<!ENTITY profilerUI.enableMemory.tooltiptext "Record memory consumption while profiling.">
-
-<!-- LOCALIZATION NOTE (profilerUI.enableAllocations): This string
-  -  is displayed next to a checkbox determining whether or not allocation
-  -  measurements are enabled. -->
-<!ENTITY profilerUI.enableAllocations             "Record Allocations">
-<!ENTITY profilerUI.enableAllocations.tooltiptext "Record Object allocations while profiling.">
-
-<!-- LOCALIZATION NOTE (profilerUI.enableFramerate): This string
-  -  is displayed next to a checkbox determining whether or not framerate
-  -  is recorded. -->
-<!ENTITY profilerUI.enableFramerate             "Record Framerate">
-<!ENTITY profilerUI.enableFramerate.tooltiptext "Record framerate while profiling.">
-
-<!-- LOCALIZATION NOTE (profilerUI.enableJITOptimizations): This string
-  -  is displayed next to a checkbox determining whether or not JIT optimization data
-  -  should be recorded. -->
-<!ENTITY profilerUI.enableJITOptimizations             "Record JIT Optimizations">
-<!ENTITY profilerUI.enableJITOptimizations.tooltiptext "Record JIT optimization data sampled in each JavaScript frame.">
-
-<!-- LOCALIZATION NOTE (profilerUI.JITOptimizationsTitle): This string
-  -  is displayed as the title of the JIT Optimizations panel. -->
-<!ENTITY profilerUI.JITOptimizationsTitle "JIT Optimizations">
-
-<!-- LOCALIZATION NOTE (profilerUI.console.recordingNoticeStart/recordingNoticeEnd):
-  -  This string is displayed when a recording is selected that started via console.profile.
-  -  Wraps the command used to start, like "Currently recording via console.profile("label")" -->
-<!ENTITY profilerUI.console.recordingNoticeStart "Currently recording via">
-<!ENTITY profilerUI.console.recordingNoticeEnd   "">
-
-<!-- LOCALIZATION NOTE (profilerUI.console.stopCommandStart/stopCommandEnd):
-  -  This string is displayed when a recording is selected that started via console.profile.
-  -  Indicates how to stop the recording, wrapping the command, like
-  -  "Stop recording by entering console.profilEnd("label") into the console." -->
-<!ENTITY profilerUI.console.stopCommandStart "Stop recording by entering">
-<!ENTITY profilerUI.console.stopCommandEnd   "into the console.">
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.properties
+++ /dev/null
@@ -1,157 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# LOCALIZATION NOTE These strings are used inside the Profiler
-# which is available from the Web Developer sub-menu -> 'Profiler'.
-# The correct localization of this file might be to keep it in
-# English, or another language commonly spoken among web developers.
-# 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 (profiler.label):
-# This string is displayed in the title of the tab when the profiler is
-# displayed inside the developer tools window and in the Developer Tools Menu.
-profiler.label2=Performance
-
-# LOCALIZATION NOTE (profiler.panelLabel):
-# This is used as the label for the toolbox panel.
-profiler.panelLabel2=Performance Panel
-
-# LOCALIZATION NOTE (profiler2.commandkey, profiler.accesskey)
-# Used for the menuitem in the tool menu
-profiler.commandkey2=VK_F5
-profiler.accesskey=P
-
-# LOCALIZATION NOTE (profiler.tooltip3):
-# This string is displayed in the tooltip of the tab when the profiler is
-# displayed inside the developer tools window.
-# Keyboard shortcut for JS Profiler will be shown inside brackets.
-profiler.tooltip3=JavaScript Profiler (%S)
-
-# LOCALIZATION NOTE (noRecordingsText): The text to display in the
-# recordings menu when there are no recorded profiles yet.
-noRecordingsText=There are no profiles yet.
-
-# LOCALIZATION NOTE (recordingsList.itemLabel):
-# This string is displayed in the recordings list of the Profiler,
-# identifying a set of function calls.
-recordingsList.itemLabel=Recording #%S
-
-# LOCALIZATION NOTE (recordingsList.recordingLabel):
-# This string is displayed in the recordings list of the Profiler,
-# for an item that has not finished recording.
-recordingsList.recordingLabel=In progress…
-
-# LOCALIZATION NOTE (recordingsList.durationLabel):
-# This string is displayed in the recordings list of the Profiler,
-# for an item that has finished recording.
-recordingsList.durationLabel=%S ms
-
-# LOCALIZATION NOTE (recordingsList.saveLabel):
-# This string is displayed in the recordings list of the Profiler,
-# for saving an item to disk.
-recordingsList.saveLabel=Save
-
-# LOCALIZATION NOTE (profile.tab):
-# This string is displayed in the profile view for a tab, after the
-# recording has finished, as the recording 'start → stop' range in milliseconds.
-profile.tab=%1$S ms → %2$S ms
-
-# LOCALIZATION NOTE (graphs.fps):
-# This string is displayed in the framerate graph of the Profiler,
-# as the unit used to measure frames per second. This label should be kept
-# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
-graphs.fps=fps
-
-# LOCALIZATION NOTE (graphs.ms):
-# This string is displayed in the flamegraph of the Profiler,
-# as the unit used to measure time (in milliseconds). This label should be kept
-# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
-graphs.ms=ms
-
-# LOCALIZATION NOTE (category.*):
-# These strings are displayed in the categories graph of the Profiler,
-# as the legend for each block in every bar. These labels should be kept
-# AS SHORT AS POSSIBLE so they don't obstruct important parts of the graph.
-category.other=Gecko
-category.css=Styles
-category.js=JIT
-category.gc=GC
-category.network=Network
-category.graphics=Graphics
-category.storage=Storage
-category.events=Input & Events
-category.tools=Tools
-
-# LOCALIZATION NOTE (graphs.ms):
-# This string is displayed in the call tree after units of time in milliseconds.
-table.ms=ms
-
-# LOCALIZATION NOTE (graphs.ms):
-# This string is displayed in the call tree after units representing percentages.
-table.percentage=%
-
-# LOCALIZATION NOTE (table.root):
-# This string is displayed in the call tree for the root node.
-table.root=(root)
-
-# LOCALIZATION NOTE (table.idle):
-# This string is displayed in the call tree for the idle blocks.
-table.idle=(idle)
-
-# LOCALIZATION NOTE (table.url.tooltiptext):
-# This string is displayed in the call tree as the tooltip text for the url
-# labels which, when clicked, jump to the debugger.
-table.url.tooltiptext=View source in Debugger
-
-# LOCALIZATION NOTE (table.zoom.tooltiptext):
-# This string is displayed in the call tree as the tooltip text for the 'zoom'
-# buttons (small magnifying glass icons) which spawn a new tab.
-table.zoom.tooltiptext=Inspect frame in new tab
-
-# LOCALIZATION NOTE (table.view-optimizations.tooltiptext):
-# This string is displayed in the icon displayed next to frames that
-# have optimization data
-table.view-optimizations.tooltiptext=View optimizations in JIT View
-
-
-# LOCALIZATION NOTE (recordingsList.saveDialogTitle):
-# This string is displayed as a title for saving a recording to disk.
-recordingsList.saveDialogTitle=Save profile…
-
-# LOCALIZATION NOTE (recordingsList.saveDialogJSONFilter):
-# This string is displayed as a filter for saving a recording to disk.
-recordingsList.saveDialogJSONFilter=JSON Files
-
-# LOCALIZATION NOTE (recordingsList.saveDialogAllFilter):
-# This string is displayed as a filter for saving a recording to disk.
-recordingsList.saveDialogAllFilter=All Files
-
-# LOCALIZATION NOTE (jit.optimizationFailure):
-# This string is displayed in a tooltip when no JIT optimizations were detected.
-jit.optimizationFailure=Optimization failed
-
-# LOCALIZATION NOTE (jit.samples2):
-# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-# This string is displayed for the unit representing the number of times a
-# frame is sampled.
-# "#1" represents the number of samples
-# example: 30 samples
-jit.samples2=#1 sample;#1 samples
-
-# LOCALIZATION NOTE (jit.empty):
-# This string is displayed when there are no JIT optimizations to display.
-jit.empty=No JIT optimizations recorded for this frame.
-
-# LOCALIZATION NOTE (consoleProfile.recordingNotice/stopCommand):
-# These strings are displayed when a recording is in progress, that was started from the console.
-# TODO REMOVE
-consoleProfile.recordingNotice=Currently recording profile "%S".
-# TODO REMOVE
-consoleProfile.stopCommand=Stop profiling by typing \"console.profileEnd(\'%S\')\" into the console.
-
-# LOCALIZATION NOTE (profiler.bufferStatus):
-# This string is displayed illustrating how full the profiler's circular buffer is.
-profiler.bufferStatus=Buffer capacity: %S%
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.dtd
+++ /dev/null
@@ -1,43 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- LOCALIZATION NOTE : FILE This file contains the Timeline strings -->
-<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
-
-<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
-  - keep it in English, or another language commonly spoken among web developers.
-  - 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.recordLabel): This string is displayed
-  -  as a label to signal that a recording is in progress. -->
-<!ENTITY timelineUI.recordLabel "Recording…">
-
-<!-- LOCALIZATION NOTE (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.memoryCheckbox.tooltip): This string
-  -  is displayed next to the memory checkbox -->
-<!ENTITY timelineUI.memoryCheckbox.tooltip "Enable memory measurements">
-
-<!-- LOCALIZATION NOTE (timelineUI.filterButton.tooltip): This string
-  -  is displayed next to the filter button-->
-<!ENTITY timelineUI.filterButton.tooltip "Select what data to display">
-
-<!-- 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">
-<!ENTITY timelineUI.stopNotice2    "button again to stop recording.">
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.properties
+++ /dev/null
@@ -1,79 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# LOCALIZATION NOTE These strings are used inside the Timeline
-# which is available from the Web Developer sub-menu -> 'Timeline'.
-# The correct localization of this file might be to keep it in
-# English, or another language commonly spoken among web developers.
-# 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 (timeline.label):
-# This string is displayed in the title of the tab when the timeline is
-# displayed inside the developer tools window and in the Developer Tools Menu.
-timeline.label=Timeline
-
-# LOCALIZATION NOTE (timeline.panelLabel):
-# This is used as the label for the toolbox panel.
-timeline.panelLabel=Timeline Panel
-
-# LOCALIZATION NOTE (timeline.tooltip):
-# This string is displayed in the tooltip of the tab when the timeline is
-# displayed inside the developer tools window.
-timeline.tooltip=Performance Timeline
-
-# LOCALIZATION NOTE (timeline.tick):
-# This string is displayed in the timeline overview, for delimiting ticks
-# by time, in milliseconds.
-timeline.tick=%S ms
-
-# LOCALIZATION NOTE (timeline.records):
-# This string is displayed in the timeline waterfall, as a title for the menu.
-timeline.records=RECORDS
-
-# LOCALIZATION NOTE (timeline.label.*):
-# These strings are displayed in the timeline waterfall, identifying markers.
-# We want to use the same wording as Google Chrome
-timeline.label.styles2=Recalculate Style
-timeline.label.reflow2=Layout
-timeline.label.paint=Paint
-timeline.label.javascript2=Function Call
-timeline.label.parseHTML=Parse HTML
-timeline.label.parseXML=Parse XML
-timeline.label.domevent=DOM Event
-timeline.label.consoleTime=Console
-timeline.label.garbageCollection=GC Event
-timeline.label.timestamp=Timestamp
-timeline.label.unknown=Unknown
-
-# 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)
-
-# LOCALIZATION NOTE (time.markerDetail.*):
-# Strings used in the waterfall sidebar.
-timeline.markerDetail.start=Start:
-timeline.markerDetail.end=End:
-timeline.markerDetail.duration=Duration:
-timeline.markerDetail.consoleTimerName=Timer Name:
-timeline.markerDetail.DOMEventType=Event Type:
-timeline.markerDetail.DOMEventPhase=Phase:
-timeline.markerDetail.DOMEventTargetPhase=Target
-timeline.markerDetail.DOMEventCapturingPhase=Capture
-timeline.markerDetail.DOMEventBubblingPhase=Bubbling
-timeline.markerDetail.stack=Stack:
-timeline.markerDetail.startStack=Stack at start:
-timeline.markerDetail.endStack=Stack at end:
-timeline.markerDetail.unknownFrame=<unknown location>
-timeline.markerDetail.asyncStack=(Async: %S)
-timeline.markerDetail.causeName=Cause:
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -112,16 +112,19 @@ add_contact_button=Add Contact
 ### the user enters an invalid email address, preventing the addition of the
 ### contact.
 valid_email_text_description=Please enter a valid email address
 
 ## LOCALIZATION NOTE (add_or_import_contact_title): This is the subtitle of the
 ## panel.
 add_or_import_contact_title=Add or Import Contact
 import_contacts_button2=Import from Google
+## LOCALIZATION NOTE (import_contacts_button3): Text for button used to import
+## contacts into the contact list.
+import_contacts_button3=Import
 importing_contacts_progress_button=Importing…
 import_contacts_failure_message=Some contacts could not be imported. Please try again.
 ## LOCALIZATION NOTE(import_contacts_success_message): Success notification message
 ## when user's contacts have been successfully imported.
 ## Semicolon-separated list of plural forms. See:
 ## http://developer.mozilla.org/en/docs/Localization_and_Plurals
 ## In this item, don't translate the part between {{..}}
 import_contacts_success_message={{total}} contact was successfully imported.;{{total}} contacts were successfully imported.
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -54,27 +54,26 @@
     locale/browser/devtools/storage.properties        (%chrome/browser/devtools/storage.properties)
     locale/browser/devtools/styleeditor.properties    (%chrome/browser/devtools/styleeditor.properties)
     locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
     locale/browser/devtools/VariablesView.dtd         (%chrome/browser/devtools/VariablesView.dtd)
     locale/browser/devtools/sourceeditor.properties   (%chrome/browser/devtools/sourceeditor.properties)
     locale/browser/devtools/sourceeditor.dtd          (%chrome/browser/devtools/sourceeditor.dtd)
-    locale/browser/devtools/profiler.dtd              (%chrome/browser/devtools/profiler.dtd)
-    locale/browser/devtools/profiler.properties       (%chrome/browser/devtools/profiler.properties)
     locale/browser/devtools/promisedebugger.dtd       (%chrome/browser/devtools/promisedebugger.dtd)
     locale/browser/devtools/promisedebugger.properties  (%chrome/browser/devtools/promisedebugger.properties)
+    locale/browser/devtools/performance.dtd           (%chrome/browser/devtools/performance.dtd)
+    locale/browser/devtools/performance.properties    (%chrome/browser/devtools/performance.properties)
     locale/browser/devtools/layoutview.dtd            (%chrome/browser/devtools/layoutview.dtd)
     locale/browser/devtools/responsiveUI.properties   (%chrome/browser/devtools/responsiveUI.properties)
     locale/browser/devtools/toolbox.dtd            (%chrome/browser/devtools/toolbox.dtd)
     locale/browser/devtools/toolbox.properties     (%chrome/browser/devtools/toolbox.properties)
     locale/browser/devtools/inspector.dtd          (%chrome/browser/devtools/inspector.dtd)
-    locale/browser/devtools/timeline.dtd           (%chrome/browser/devtools/timeline.dtd)
-    locale/browser/devtools/timeline.properties    (%chrome/browser/devtools/timeline.properties)
+    locale/browser/devtools/markers.properties     (%chrome/browser/devtools/markers.properties)
     locale/browser/devtools/projecteditor.properties     (%chrome/browser/devtools/projecteditor.properties)
     locale/browser/devtools/eyedropper.properties     (%chrome/browser/devtools/eyedropper.properties)
     locale/browser/devtools/connection-screen.dtd  (%chrome/browser/devtools/connection-screen.dtd)
     locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
     locale/browser/devtools/font-inspector.dtd     (%chrome/browser/devtools/font-inspector.dtd)
     locale/browser/devtools/har.properties         (%chrome/browser/devtools/har.properties)
     locale/browser/devtools/app-manager.dtd        (%chrome/browser/devtools/app-manager.dtd)
     locale/browser/devtools/app-manager.properties (%chrome/browser/devtools/app-manager.properties)
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -257,16 +257,17 @@ bool nsContentUtils::sTrustedFullScreenO
 bool nsContentUtils::sIsCutCopyAllowed = true;
 bool nsContentUtils::sIsPerformanceTimingEnabled = false;
 bool nsContentUtils::sIsResourceTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
 bool nsContentUtils::sEncodeDecodeURLHash = false;
 bool nsContentUtils::sGettersDecodeURLHash = false;
 bool nsContentUtils::sPrivacyResistFingerprinting = false;
+bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
 
 nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
 nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
 bool nsContentUtils::sFragmentParsingActive = false;
 
@@ -548,16 +549,19 @@ nsContentUtils::Init()
 
   Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
                                "privacy.resistFingerprinting", false);
 
   Preferences::AddUintVarCache(&sHandlingInputTimeout,
                                "dom.event.handling-user-input-time-limit",
                                1000);
 
+  Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications,
+                               "dom.performance.enable_notify_performance_timing", false);
+
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
   Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
                                "browser.dom.window.dump.enabled");
 #endif
 
   Element::InitCCCallbacks();
 
   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1944,16 +1944,24 @@ public:
    * Returns true if the performance timing APIs are enabled.
    */
   static bool IsResourceTimingEnabled()
   {
     return sIsResourceTimingEnabled;
   }
 
   /*
+   * Returns true if notification should be sent for peformance timing events.
+   */
+  static bool SendPerformanceTimingNotifications()
+  {
+    return sSendPerformanceTimingNotifications;
+  }
+
+  /*
    * Returns true if URL setters should percent encode the Hash/Ref segment
    * and getters should return the percent decoded value of the segment
    */
   static bool EncodeDecodeURLHash()
   {
     return sEncodeDecodeURLHash;
   }
 
@@ -2539,16 +2547,17 @@ private:
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
   static bool sIsUserTimingLoggingEnabled;
   static bool sIsExperimentalAutocompleteEnabled;
   static bool sEncodeDecodeURLHash;
   static bool sGettersDecodeURLHash;
   static bool sPrivacyResistFingerprinting;
+  static bool sSendPerformanceTimingNotifications;
 
   static nsHtml5StringParser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   /**
    * True if there's a fragment parser activation on the stack.
    */
--- a/dom/base/nsPerformance.cpp
+++ b/dom/base/nsPerformance.cpp
@@ -16,16 +16,17 @@
 #include "nsIURI.h"
 #include "nsThreadUtils.h"
 #include "PerformanceEntry.h"
 #include "PerformanceMark.h"
 #include "PerformanceMeasure.h"
 #include "PerformanceResourceTiming.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/PerformanceBinding.h"
+#include "mozilla/dom/PerformanceEntryEvent.h"
 #include "mozilla/dom/PerformanceTimingBinding.h"
 #include "mozilla/dom/PerformanceNavigationBinding.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/TimeStamp.h"
 #include "js/HeapAPI.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
@@ -733,24 +734,34 @@ nsPerformance::IsEnabled(JSContext* aCx,
   return runnable->IsEnabled();
 }
 
 void
 nsPerformance::InsertUserEntry(PerformanceEntry* aEntry)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (nsContentUtils::IsUserTimingLoggingEnabled()) {
-    nsAutoCString uri;
+  nsAutoCString uri;
+  uint64_t markCreationEpoch = 0;
+  if (nsContentUtils::IsUserTimingLoggingEnabled() ||
+      nsContentUtils::SendPerformanceTimingNotifications()) {
     nsresult rv = GetOwner()->GetDocumentURI()->GetHost(uri);
     if(NS_FAILED(rv)) {
       // If we have no URI, just put in "none".
       uri.AssignLiteral("none");
     }
-    PerformanceBase::LogEntry(aEntry, uri);
+    markCreationEpoch = static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC);
+
+    if (nsContentUtils::IsUserTimingLoggingEnabled()) {
+      PerformanceBase::LogEntry(aEntry, uri);
+    }
+  }
+
+  if (nsContentUtils::SendPerformanceTimingNotifications()) {
+    TimingNotification(aEntry, uri, markCreationEpoch);
   }
 
   PerformanceBase::InsertUserEntry(aEntry);
 }
 
 DOMHighResTimeStamp
 nsPerformance::DeltaFromNavigationStart(DOMHighResTimeStamp aTime)
 {
@@ -982,16 +993,39 @@ PerformanceBase::LogEntry(PerformanceEnt
           NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(),
           NS_ConvertUTF16toUTF8(aEntry->GetName()).get(),
           aEntry->StartTime(),
           aEntry->Duration(),
           static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
 }
 
 void
+PerformanceBase::TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t aEpoch)
+{
+  PerformanceEntryEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mName = aEntry->GetName();
+  init.mEntryType = aEntry->GetEntryType();
+  init.mStartTime = aEntry->StartTime();
+  init.mDuration = aEntry->Duration();
+  init.mEpoch = aEpoch;
+  init.mOrigin = NS_ConvertUTF8toUTF16(aOwner.BeginReading());
+
+  nsRefPtr<PerformanceEntryEvent> perfEntryEvent =
+    PerformanceEntryEvent::Constructor(this, NS_LITERAL_STRING("performanceentry"), init);
+
+  nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
+  if (et) {
+    bool dummy = false;
+    et->DispatchEvent(perfEntryEvent, &dummy);
+  }
+}
+
+void
 PerformanceBase::InsertUserEntry(PerformanceEntry* aEntry)
 {
   mUserEntries.InsertElementSorted(aEntry,
                                    PerformanceEntryComparator());
 }
 
 void
 PerformanceBase::SetResourceTimingBufferSize(uint64_t aMaxSize)
--- a/dom/base/nsPerformance.h
+++ b/dom/base/nsPerformance.h
@@ -346,16 +346,17 @@ protected:
   GetPerformanceTimingFromString(const nsAString& aTimingName) = 0;
 
   bool IsResourceEntryLimitReached() const
   {
     return mResourceEntries.Length() >= mResourceTimingBufferSize;
   }
 
   void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const;
+  void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t epoch);
 
 private:
   nsTArray<nsRefPtr<PerformanceEntry>> mUserEntries;
   nsTArray<nsRefPtr<PerformanceEntry>> mResourceEntries;
 
   uint64_t mResourceTimingBufferSize;
   static const uint64_t kDefaultResourceTimingBufferSize = 150;
 };
--- a/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js
@@ -5,23 +5,30 @@ MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = "head.js";
 
 function verifyVoiceCellLocationInfo(aLac, aCid) {
   let cell = mobileConnection.voice.cell;
   ok(cell, "location available");
 
   is(cell.gsmLocationAreaCode, aLac, "check voice.cell.gsmLocationAreaCode");
   is(cell.gsmCellId, aCid, "check voice.cell.gsmCellId");
+
+  // TODO: Since gecko doesn't reset these values below to their invalid values,
+  // the tests below will fail after we once change to CDMA mode. Please refer
+  // to Bug 1190274 for more information.
+
+  /*
   is(cell.cdmaBaseStationId, -1, "check voice.cell.cdmaBaseStationId");
   is(cell.cdmaBaseStationLatitude, -2147483648,
      "check voice.cell.cdmaBaseStationLatitude");
   is(cell.cdmaBaseStationLongitude, -2147483648,
      "check voice.cell.cdmaBaseStationLongitude");
   is(cell.cdmaSystemId, -1, "check voice.cell.cdmaSystemId");
   is(cell.cdmaNetworkId, -1, "check voice.cell.cdmaNetworkId");
+  */
 }
 
 /* Test Voice Cell Location Info Change */
 function testVoiceCellLocationUpdate(aLac, aCid) {
   // Set emulator's lac/cid and wait for 'onvoicechange' event.
   log("Test cell location with lac=" + aLac + " and cid=" + aCid);
 
   return setEmulatorGsmLocationAndWait(aLac, aCid, true, false)
--- a/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js
@@ -1,33 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = "head.js";
 
-const INITIAL_STATES = {
-  state: "registered",
-  connected: true,
-  emergencyCallsOnly: false,
-  roaming: false,
-  signalStrength: -99,
-  relSignalStrength: 44,
-
-  cell: {
-    gsmLocationAreaCode: 0,
-    gsmCellId: 0,
-    cdmaBaseStationId: -1,
-    cdmaBaseStationLatitude: -2147483648,
-    cdmaBaseStationLongitude: -2147483648,
-    cdmaSystemId: -1,
-    cdmaNetworkId: -1,
-  }
-};
-
 const TEST_DATA = [{
     // Test state becomes to "unregistered"
     state: "unregistered",
     expected: {
       state: "notSearching",
       connected: false,
       emergencyCallsOnly: true,
       roaming: false,
@@ -121,18 +102,16 @@ function testVoiceStateUpdate(aNewState,
   // Set emulator's lac/cid and wait for 'onvoicechange' event.
   return setEmulatorVoiceDataStateAndWait("voice", aNewState)
     .then(() => verifyVoiceInfo(aExpected));
 }
 
 startTestCommon(function() {
   log("Test initial voice connection info");
 
-  verifyVoiceInfo(INITIAL_STATES);
-
   let promise = Promise.resolve();
   for (let i = 0; i < TEST_DATA.length; i++) {
     let entry = TEST_DATA[i];
     promise =
       promise.then(testVoiceStateUpdate.bind(null, entry.state, entry.expected));
   }
 
   return promise;
--- a/dom/telephony/test/marionette/head.js
+++ b/dom/telephony/test/marionette/head.js
@@ -272,25 +272,77 @@ let emulator = (function() {
         resolve();
       }, function() {
         return telephony.calls.length === 0;
       });
     });
   }
 
   /**
+   * @param aVoiceType
+   *        The voice type of a mobileConnection, which can be obtained from
+   *        |<mobileConnection>.voice.type|.
+   * @return A string with format of the emulator voice tech.
+   */
+  function voiceTypeToTech(aVoiceType) {
+    switch(aVoiceType) {
+        case "gsm":
+        case "gprs":
+        case "edge":
+          return "gsm";
+
+        case "umts":
+        case "hsdpa":
+        case "hsupa":
+        case "hspa":
+        case "hspa+":
+          return "wcdma";
+
+        case "is95a":
+        case "is95b":
+        case "1xrtt":
+          return "cdma";
+
+        case "evdo0":
+        case "evdoa":
+        case "evdob":
+          return "evdo";
+
+        case "ehrpd":
+        case "lte":
+          return "lte";
+
+        default:
+          return null;
+      }
+  }
+
+  /**
    * @return Promise
    */
   function changeModemTech(aTech, aPreferredMask) {
-    return Promise.resolve()
+    let mobileConn = navigator.mozMobileConnections[0];
+
+    function isTechMatched() {
+      return aTech === voiceTypeToTech(mobileConn.voice.type);
+    }
+
+    let promise1 = isTechMatched() ? Promise.resolve()
+                                   : waitForEvent(mobileConn,
+                                                  "voicechange",
+                                                  isTechMatched);
+
+    let promise2 = Promise.resolve()
       .then(() => emulator.runCmd("modem tech " + aTech + " " + aPreferredMask))
       .then(() => emulator.runCmd("modem tech"))
       .then(result => is(result[0],
                          aTech + " " + aPreferredMask,
                          "Check modem 'tech/preferred mask'"));
+
+    return Promise.all([promise1, promise2]);
   }
 
   /**
    * @return Promise
    */
   function clearCalls() {
     log("Clear existing calls.");
 
--- a/dom/telephony/test/marionette/manifest.ini
+++ b/dom/telephony/test/marionette/manifest.ini
@@ -40,16 +40,17 @@ qemu = true
 [test_mmi_change_pin.js]
 [test_mmi_change_pin2.js]
 [test_mmi_clip.js]
 [test_mmi_clir.js]
 [test_mmi_imei.js]
 [test_mmi_unlock_puk.js]
 [test_mmi_unlock_puk2.js]
 [test_mmi_ussd.js]
+[test_modem_switch_tech.js]
 [test_multiple_hold.js]
 [test_outgoing_already_held.js]
 [test_outgoing_answer_hangup_oncallschanged.js]
 [test_outgoing_answer_radio_off.js]
 [test_outgoing_auto_hold.js]
 [test_outgoing_badNumber.js]
 [test_outgoing_basic_operations.js]
 [test_outgoing_busy.js]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PerformanceEntryEvent.webidl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+dictionary PerformanceEntryEventInit : EventInit
+{
+  DOMString name = "";
+  DOMString entryType = "";
+  DOMHighResTimeStamp startTime = 0;
+  DOMHighResTimeStamp duration = 0;
+  double epoch = 0;
+  DOMString origin = "";
+};
+
+[Constructor(DOMString type, optional PerformanceEntryEventInit eventInitDict),
+ ChromeOnly]
+interface PerformanceEntryEvent : Event
+{
+  readonly attribute DOMString name;
+  readonly attribute DOMString entryType;
+  readonly attribute DOMHighResTimeStamp startTime;
+  readonly attribute DOMHighResTimeStamp duration;
+  readonly attribute double epoch;
+  readonly attribute DOMString origin;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -774,16 +774,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'MozMmsEvent.webidl',
     'MozOtaStatusEvent.webidl',
     'MozSettingsEvent.webidl',
     'MozSettingsTransactionEvent.webidl',
     'MozSmsEvent.webidl',
     'MozStkCommandEvent.webidl',
     'MozVoicemailEvent.webidl',
     'PageTransitionEvent.webidl',
+    'PerformanceEntryEvent.webidl',
     'PluginCrashedEvent.webidl',
     'PopStateEvent.webidl',
     'PopupBlockedEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
     'ScrollViewChangeEvent.webidl',
     'SelectionStateChangedEvent.webidl',
     'StyleRuleChangeEvent.webidl',
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -784,17 +784,17 @@ public class AndroidFxAccount {
     protected void onReceiveResult(int resultCode, Bundle bundle) {
       super.onReceiveResult(resultCode, bundle);
       switch (resultCode) {
         case Activity.RESULT_OK:
           final String resultData = bundle.getString(FxAccountProfileService.KEY_RESULT_STRING);
           updateBundleValues(BUNDLE_KEY_PROFILE_JSON, resultData);
           Logger.info(LOG_TAG, "Profile JSON fetch succeeeded!");
           FxAccountUtils.pii(LOG_TAG, "Profile JSON fetch returned: " + resultData);
-          LocalBroadcastManager.getInstance(context).sendBroadcast(makeDeletedAccountIntent());
+          LocalBroadcastManager.getInstance(context).sendBroadcast(makeProfileJSONUpdatedIntent());
           break;
         case Activity.RESULT_CANCELED:
           Logger.warn(LOG_TAG, "Failed to fetch profile JSON; ignoring.");
           break;
         default:
           Logger.warn(LOG_TAG, "Invalid result code received; ignoring.");
           break;
       }
--- a/mobile/android/base/home/BrowserSearch.java
+++ b/mobile/android/base/home/BrowserSearch.java
@@ -46,17 +46,16 @@ import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewStub;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
 import android.view.animation.TranslateAnimation;
 import android.widget.AdapterView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/SearchEngineAdapter.java
@@ -0,0 +1,122 @@
+package org.mozilla.gecko.home;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import org.mozilla.gecko.R;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SearchEngineAdapter
+        extends RecyclerView.Adapter<SearchEngineAdapter.SearchEngineViewHolder> {
+
+    private static final String LOGTAG = SearchEngineAdapter.class.getSimpleName();
+
+    private static final int VIEW_TYPE_SEARCH_ENGINE = 0;
+    private static final int VIEW_TYPE_LABEL = 1;
+    private final Context mContext;
+
+    private int mContainerWidth;
+    private List<SearchEngine> mSearchEngines = Collections.emptyList();
+
+    public void setSearchEngines(List<SearchEngine> searchEngines) {
+        mSearchEngines = searchEngines;
+        notifyDataSetChanged();
+    }
+
+    /**
+     * The container width is used for setting the appropriate calculated amount of width that
+     * a search engine icon can have. This varies depending on the space available in the
+     * {@link SearchEngineBar}. The setter exists for this attribute, in creating the view in the
+     * adapter after said calculation is done when the search bar is created.
+     * @param iconContainerWidth Width of each search icon.
+     */
+    void setIconContainerWidth(int iconContainerWidth) {
+        mContainerWidth = iconContainerWidth;
+    }
+
+    public static class SearchEngineViewHolder extends RecyclerView.ViewHolder {
+        final private ImageView faviconView;
+
+        public void bindItem(SearchEngine searchEngine) {
+            faviconView.setImageBitmap(searchEngine.getIcon());
+            final String desc = itemView.getResources().getString(R.string.search_bar_item_desc,
+                searchEngine.getEngineIdentifier());
+            itemView.setContentDescription(desc);
+        }
+
+        public SearchEngineViewHolder(View itemView) {
+            super(itemView);
+            faviconView = (ImageView) itemView.findViewById(R.id.search_engine_icon);
+        }
+    }
+
+    public SearchEngineAdapter(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return position == 0 ? VIEW_TYPE_LABEL : VIEW_TYPE_SEARCH_ENGINE;
+    }
+
+    public SearchEngine getItem(int position) {
+        // We omit the first position which is where the label currently is.
+        return position == 0 ? null : mSearchEngines.get(position - 1);
+    }
+
+    @Override
+    public SearchEngineViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        switch (viewType) {
+            case VIEW_TYPE_LABEL:
+                return new SearchEngineViewHolder(createLabelView(parent));
+            case VIEW_TYPE_SEARCH_ENGINE:
+                return new SearchEngineViewHolder(createSearchEngineView(parent));
+            default:
+                throw new IllegalArgumentException("Unknown view type: "  + viewType);
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(SearchEngineViewHolder holder, int position) {
+        if (position != 0) {
+            holder.bindItem(getItem(position));
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSearchEngines.size() + 1;
+    }
+
+    private View createLabelView(ViewGroup parent) {
+        View view = LayoutInflater.from(mContext)
+                        .inflate(R.layout.search_engine_bar_label, parent, false);
+        final Drawable icon = DrawableCompat.wrap(
+                ContextCompat.getDrawable(mContext, R.drawable.search_icon_active).mutate());
+        DrawableCompat.setTint(icon, mContext.getResources().getColor(R.color.disabled_grey));
+
+        final ImageView iconView = (ImageView) view.findViewById(R.id.search_engine_label);
+        iconView.setImageDrawable(icon);
+        return view;
+    }
+
+    private View createSearchEngineView(ViewGroup parent) {
+        View view = LayoutInflater.from(mContext)
+                    .inflate(R.layout.search_engine_bar_item, parent, false);
+
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        params.width = mContainerWidth;
+        view.setLayoutParams(params);
+
+        return view;
+    }
+}
--- a/mobile/android/base/home/SearchEngineBar.java
+++ b/mobile/android/base/home/SearchEngineBar.java
@@ -1,215 +1,146 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
- package org.mozilla.gecko.home;
+package org.mozilla.gecko.home;
 
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
 
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.widget.TwoWayView;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 
-import java.util.ArrayList;
 import java.util.List;
 
-public class SearchEngineBar extends TwoWayView
-                             implements AdapterView.OnItemClickListener {
-    private static final String LOGTAG = "Gecko" + SearchEngineBar.class.getSimpleName();
+public class SearchEngineBar extends RecyclerView
+        implements RecyclerViewItemClickListener.OnClickListener {
+    private static final String LOGTAG = SearchEngineBar.class.getSimpleName();
 
     private static final float ICON_CONTAINER_MIN_WIDTH_DP = 72;
     private static final float LABEL_CONTAINER_WIDTH_DP = 48;
     private static final float DIVIDER_HEIGHT_DP = 1;
 
     public interface OnSearchBarClickListener {
-        public void onSearchBarClickListener(SearchEngine searchEngine);
+        void onSearchBarClickListener(SearchEngine searchEngine);
     }
 
-    private final SearchEngineAdapter adapter;
-    private final Paint dividerPaint;
-    private final float minIconContainerWidth;
-    private final float dividerHeight;
-    private final int labelContainerWidth;
+    private final SearchEngineAdapter mAdapter;
+    private final LinearLayoutManager mLayoutManager;
+    private final Paint mDividerPaint;
+    private final float mMinIconContainerWidth;
+    private final float mDividerHeight;
+    private final int mLabelContainerWidth;
 
-    private int iconContainerWidth;
-    private OnSearchBarClickListener onSearchBarClickListener;
+    private int mIconContainerWidth;
+    private OnSearchBarClickListener mOnSearchBarClickListener;
 
     public SearchEngineBar(final Context context, final AttributeSet attrs) {
         super(context, attrs);
 
-        dividerPaint = new Paint();
-        dividerPaint.setColor(getResources().getColor(R.color.divider_light));
-        dividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        mDividerPaint = new Paint();
+        mDividerPaint.setColor(getResources().getColor(R.color.divider_light));
+        mDividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
 
         final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
-        minIconContainerWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_CONTAINER_MIN_WIDTH_DP, displayMetrics);
-        dividerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DIVIDER_HEIGHT_DP, displayMetrics);
-        labelContainerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, LABEL_CONTAINER_WIDTH_DP, displayMetrics);
+        mMinIconContainerWidth = TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, ICON_CONTAINER_MIN_WIDTH_DP, displayMetrics);
+        mDividerHeight = TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, DIVIDER_HEIGHT_DP, displayMetrics);
+        mLabelContainerWidth = Math.round(TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, LABEL_CONTAINER_WIDTH_DP, displayMetrics));
+
+        mIconContainerWidth = Math.round(mMinIconContainerWidth);
 
-        iconContainerWidth =  (int) minIconContainerWidth;
+        mAdapter = new SearchEngineAdapter(context);
+        mAdapter.setIconContainerWidth(mIconContainerWidth);
+        mLayoutManager = new LinearLayoutManager(context);
+        mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
 
-        adapter = new SearchEngineAdapter();
-        setAdapter(adapter);
-        setOnItemClickListener(this);
+        setAdapter(mAdapter);
+        setLayoutManager(mLayoutManager);
+        addOnItemTouchListener(new RecyclerViewItemClickListener(context, this, this));
     }
 
-    @Override
-    public void onItemClick(final AdapterView<?> parent, final View view, final int position,
-            final long id) {
-        if (onSearchBarClickListener == null) {
-            throw new IllegalStateException(
-                    OnSearchBarClickListener.class.getSimpleName() + " is not initialized");
-        }
-
-        if (position == 0) {
-            // Ignore click on label
-            return;
-        }
-
-        final SearchEngine searchEngine = adapter.getItem(position);
-        onSearchBarClickListener.onSearchBarClickListener(searchEngine);
+    public void setSearchEngines(List<SearchEngine> searchEngines) {
+        mAdapter.setSearchEngines(searchEngines);
     }
 
-    protected void setOnSearchBarClickListener(final OnSearchBarClickListener listener) {
-        onSearchBarClickListener = listener;
-    }
-
-    protected void setSearchEngines(final List<SearchEngine> searchEngines) {
-        adapter.setSearchEngines(searchEngines);
+    public void setOnSearchBarClickListener(OnSearchBarClickListener listener) {
+        mOnSearchBarClickListener = listener;
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        final int searchEngineCount = adapter.getCount() - 1;
+        final int searchEngineCount = mAdapter.getItemCount() - 1;
 
         if (searchEngineCount > 0) {
-            final int availableWidth = getMeasuredWidth() - labelContainerWidth;
+            final int availableWidth = getMeasuredWidth() - mLabelContainerWidth;
             final double searchEnginesToDisplay;
 
-            if (searchEngineCount * minIconContainerWidth <= availableWidth) {
+            if (searchEngineCount * mMinIconContainerWidth <= availableWidth) {
                 // All search engines fit int: So let's just display all.
                 searchEnginesToDisplay = searchEngineCount;
             } else {
                 // If only (n) search engines fit into the available space then display (n - 0.5): The last search
                 // engine will be cut-off to show ability to scroll this view
-
-                searchEnginesToDisplay = Math.floor(availableWidth / minIconContainerWidth) - 0.5;
+                searchEnginesToDisplay = Math.floor(availableWidth / mMinIconContainerWidth) - 0.5;
             }
 
             // Use all available width and spread search engine icons
             final int availableWidthPerContainer = (int) (availableWidth / searchEnginesToDisplay);
 
-            if (availableWidthPerContainer != iconContainerWidth) {
-                iconContainerWidth = availableWidthPerContainer;
-                adapter.notifyDataSetChanged();
+            if (availableWidthPerContainer != mIconContainerWidth) {
+                mIconContainerWidth = availableWidthPerContainer;
             }
+            mAdapter.setIconContainerWidth(mIconContainerWidth);
         }
     }
 
     @Override
-    protected void onDraw(Canvas canvas) {
+    public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
-        canvas.drawRect(0, 0, getWidth(), dividerHeight, dividerPaint);
+        canvas.drawRect(0, 0, getWidth(), mDividerHeight, mDividerPaint);
     }
 
-    public class SearchEngineAdapter extends BaseAdapter {
-        private static final int VIEW_TYPE_SEARCH_ENGINE = 0;
-        private static final int VIEW_TYPE_LABEL = 1;
-
-        List<SearchEngine> searchEngines = new ArrayList<>();
-
-        public void setSearchEngines(final List<SearchEngine> searchEngines) {
-            this.searchEngines = searchEngines;
-            notifyDataSetChanged();
+    @Override
+    public void onClick(View view, int position) {
+        if (mOnSearchBarClickListener == null) {
+            throw new IllegalStateException(
+                    OnSearchBarClickListener.class.getSimpleName() + " is not initializer."
+            );
         }
 
-        @Override
-        public int getCount() {
-            // Adding offset for label at position 0 (Bug 1172071)
-            return searchEngines.size() + 1;
-        }
-
-        @Override
-        public SearchEngine getItem(final int position) {
-            // Returning null for the label at position 0 (Bug 1172071)
-            return position == 0 ? null : searchEngines.get(position - 1);
-        }
-
-        @Override
-        public long getItemId(final int position) {
-            return position;
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            return position == 0 ? VIEW_TYPE_LABEL : VIEW_TYPE_SEARCH_ENGINE;
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return 2;
+        if (position == 0) {
+            return;
         }
 
-        @Override
-        public View getView(final int position, final View convertView, final ViewGroup parent) {
-            if (position == 0) {
-                return getLabelView(convertView, parent);
-            } else {
-                return getSearchEngineView(position, convertView, parent);
-            }
-        }
+        final SearchEngine searchEngine = mAdapter.getItem(position);
+        mOnSearchBarClickListener.onSearchBarClickListener(searchEngine);
+    }
 
-        private View getLabelView(View view, final ViewGroup parent) {
-            if (view == null) {
-                view = LayoutInflater.from(getContext()).inflate(R.layout.search_engine_bar_label, parent, false);
-            }
-
-            final Drawable icon =
-                    DrawableUtil.tintDrawable(parent.getContext(), R.drawable.search_icon_active, R.color.disabled_grey);
-
-            final ImageView iconView = (ImageView) view.findViewById(R.id.search_engine_label);
-            iconView.setImageDrawable(icon);
-            iconView.setScaleType(ImageView.ScaleType.FIT_XY);
+    @Override
+    public void onLongClick(View view, int position) {
+        // do nothing
+    }
 
-            return view;
-        }
-
-        private View getSearchEngineView(final int position, View view, final ViewGroup parent) {
-            if (view == null) {
-                view = LayoutInflater.from(getContext()).inflate(R.layout.search_engine_bar_item, parent, false);
-            }
-
-            LayoutParams params = (LayoutParams) view.getLayoutParams();
-            params.width = iconContainerWidth;
-            view.setLayoutParams(params);
-
-            final ImageView faviconView = (ImageView) view.findViewById(R.id.search_engine_icon);
-            final SearchEngine searchEngine = getItem(position);
-            faviconView.setImageBitmap(searchEngine.getIcon());
-
-            final String desc = getResources().getString(R.string.search_bar_item_desc, searchEngine.getEngineIdentifier());
-            view.setContentDescription(desc);
-
-            return view;
-        }
+    /**
+     * We manually add the override for getAdapter because we see this method getting stripped
+     * out during compile time by aggressive proguard rules.
+     */
+    @RobocopTarget
+    @Override
+    public SearchEngineAdapter getAdapter() {
+        return mAdapter;
     }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -347,16 +347,17 @@ gbjar.sources += [
     'home/RecyclerViewItemClickListener.java',
     'home/RemoteTabsBaseFragment.java',
     'home/RemoteTabsExpandableListFragment.java',
     'home/RemoteTabsExpandableListState.java',
     'home/RemoteTabsPanel.java',
     'home/RemoteTabsSplitPlaneFragment.java',
     'home/RemoteTabsStaticFragment.java',
     'home/SearchEngine.java',
+    'home/SearchEngineAdapter.java',
     'home/SearchEngineBar.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SpacingDecoration.java',
     'home/TabMenuStrip.java',
     'home/TabMenuStripLayout.java',
     'home/TopSitesGridItemView.java',
--- a/mobile/android/base/resources/layout/search_engine_bar_item.xml
+++ b/mobile/android/base/resources/layout/search_engine_bar_item.xml
@@ -12,16 +12,17 @@
 
      The actual width of the FrameLayout is calculated at runtime by the
      SearchEngineBar class to spread the icons across the device's width. -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/search_engine_icon_container"
     android:layout_width="72dp"
     android:layout_height="match_parent"
+    android:clickable="true"
     android:background="@color/pressed_about_page_header_grey">
 
     <!-- Width & height are set to make the Favicons as sharp as possible
          based on asset size. -->
     <ImageView
         android:id="@+id/search_engine_icon"
         android:layout_width="24dp"
         android:layout_height="24dp"
--- a/mobile/android/base/resources/layout/search_engine_bar_label.xml
+++ b/mobile/android/base/resources/layout/search_engine_bar_label.xml
@@ -11,11 +11,11 @@
     android:layout_width="48dp"
     android:layout_height="match_parent">
 
     <ImageView
         android:id="@+id/search_engine_label"
         android:layout_width="16dp"
         android:layout_height="16dp"
         android:layout_gravity="center"
-        android:scaleType="fitCenter"/>
+        android:scaleType="fitXY"/>
 
 </FrameLayout>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4669,45 +4669,57 @@ Tab.prototype = {
       this.browser.messageManager.sendAsyncMessage("Reader:PushState", {isArticle: this.browser.isArticle});
     }
 
     // Reset state of click-to-play plugin notifications.
     clearTimeout(this.pluginDoorhangerTimeout);
     this.pluginDoorhangerTimeout = null;
     this.shouldShowPluginDoorhanger = true;
     this.clickToPlayPluginsActivated = false;
-    // Borrowed from desktop Firefox: http://mxr.mozilla.org/mozilla-central/source/browser/base/content/urlbarBindings.xml#174
-    let documentURI = contentWin.document.documentURIObject.spec
+
+    let documentURI = contentWin.document.documentURIObject.spec;
 
     // If reader mode, get the base domain for the original url.
     let strippedURI = this._stripAboutReaderURL(documentURI);
 
+    // Borrowed from desktop Firefox: http://hg.mozilla.org/mozilla-central/annotate/72835344333f/browser/base/content/urlbarBindings.xml#l236
     let matchedURL = strippedURI.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
     let baseDomain = "";
     if (matchedURL) {
       var domain = "";
       [, , domain] = matchedURL;
 
       try {
         baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
         if (!domain.endsWith(baseDomain)) {
           // getBaseDomainFromHost converts its resultant to ACE.
           let IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
           baseDomain = IDNService.convertACEtoUTF8(baseDomain);
         }
       } catch (e) {}
     }
 
+    // If we are navigating to a new location with a different host,
+    // clear any URL origin that might have been pinned to this tab.
+    let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+    let appOrigin = ss.getTabValue(this, "appOrigin");
+    if (appOrigin) {
+      let originHost = Services.io.newURI(appOrigin, null, null).host;
+      if (originHost != aLocationURI.host) {
+        // Note: going 'back' will not make this tab pinned again
+        ss.deleteTabValue(this, "appOrigin");
+      }
+    }
+
     // Update the page actions URI for helper apps.
     if (BrowserApp.selectedTab == this) {
       ExternalApps.updatePageActionUri(fixedURI);
     }
 
-    let webNav = contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIWebNavigation);
+    let webNav = contentWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
 
     let message = {
       type: "Content:LocationChange",
       tabID: this.id,
       uri: truncate(fixedURI.spec, MAX_URI_LENGTH),
       userRequested: this.userRequested || "",
       baseDomain: baseDomain,
       contentType: (contentType ? contentType : ""),
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -1191,29 +1191,29 @@ SessionStore.prototype = {
   getTabValue: function ss_getTabValue(aTab, aKey) {
     let browser = aTab.browser;
     let data = browser.__SS_extdata || {};
     return data[aKey] || "";
   },
 
   setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
     let browser = aTab.browser;
-
-    if (!browser.__SS_extdata)
+    if (!browser.__SS_extdata) {
       browser.__SS_extdata = {};
+    }
     browser.__SS_extdata[aKey] = aStringValue;
     this.saveStateDelayed();
   },
 
   deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
     let browser = aTab.browser;
-    if (browser.__SS_extdata && browser.__SS_extdata[aKey])
+    if (browser.__SS_extdata && aKey in browser.__SS_extdata) {
       delete browser.__SS_extdata[aKey];
-    else
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+      this.saveStateDelayed();
+    }
   },
 
   restoreLastSession: Task.async(function* (aSessionString) {
     let notifyMessage = "";
 
     try {
       this._restoreWindow(aSessionString);
     } catch (e) {
--- a/mobile/android/gradle/base/build.gradle
+++ b/mobile/android/gradle/base/build.gradle
@@ -25,19 +25,33 @@ android {
         }
     }
 
     sourceSets {
         main {
             java {
                 exclude 'org/mozilla/gecko/tests/**'
                 exclude 'org/mozilla/gecko/resources/**'
+
                 if (!mozconfig.substs.MOZ_CRASHREPORTER) {
                     exclude 'org/mozilla/gecko/CrashReporter.java'
                 }
+
+                if (!mozconfig.substs.MOZ_NATIVE_DEVICES) {
+                    exclude 'org/mozilla/gecko/ChromeCast.java'
+                    exclude 'org/mozilla/gecko/GeckoMediaPlayer.java'
+                    exclude 'org/mozilla/gecko/MediaPlayerManager.java'
+                }
+
+                if (mozconfig.substs.MOZ_WEBRTC) {
+                    srcDir 'src/webrtc_audio_device'
+                    srcDir 'src/webrtc_video_capture'
+                    srcDir 'src/webrtc_video_render'
+                }
+
                 // Adjust helpers are included in the preprocessed_code project.
                 exclude 'org/mozilla/gecko/adjust/**'
             }
 
             res {
                 if (mozconfig.substs.MOZ_CRASHREPORTER) {
                     srcDir "src/crashreporter/res"
                 }
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -133,16 +133,19 @@ class MachCommands(MachCommandBase):
 
         srcdir('base/build.gradle', 'mobile/android/gradle/base/build.gradle')
         srcdir('base/lint.xml', 'mobile/android/gradle/base/lint.xml')
         srcdir('base/src/main/AndroidManifest.xml', 'mobile/android/gradle/base/AndroidManifest.xml')
         srcdir('base/src/main/java/org/mozilla/gecko', 'mobile/android/base')
         srcdir('base/src/main/java/org/mozilla/mozstumbler', 'mobile/android/stumbler/java/org/mozilla/mozstumbler')
         srcdir('base/src/main/java/org/mozilla/search', 'mobile/android/search/java/org/mozilla/search')
         srcdir('base/src/main/java/org/mozilla/javaaddons', 'mobile/android/javaaddons/java/org/mozilla/javaaddons')
+        srcdir('base/src/webrtc_audio_device/java', 'media/webrtc/trunk/webrtc/modules/audio_device/android/java/src')
+        srcdir('base/src/webrtc_video_capture/java', 'media/webrtc/trunk/webrtc/modules/video_capture/android/java/src')
+        srcdir('base/src/webrtc_video_render/java', 'media/webrtc/trunk/webrtc/modules/video_render/android/java/src')
         srcdir('base/src/main/res', 'mobile/android/base/resources')
         srcdir('base/src/crashreporter/res', 'mobile/android/base/crashreporter/res')
 
         manifest_path = os.path.join(self.topobjdir, 'mobile', 'android', 'gradle.manifest')
         with FileAvoidWrite(manifest_path) as f:
             m.write(fileobj=f)
 
         self.virtualenv_manager.ensure()
--- a/mobile/android/tests/browser/robocop/AboutHomeTest.java
+++ b/mobile/android/tests/browser/robocop/AboutHomeTest.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.tests;
 
 import java.util.ArrayList;
 
 import org.mozilla.gecko.Actions;
 import org.mozilla.gecko.home.HomePager;
 
 import android.support.v4.view.ViewPager;
+import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.TabWidget;
 import android.widget.TextView;
 
@@ -203,17 +204,17 @@ abstract class AboutHomeTest extends Pix
     }
 
     private void clickAboutHomeTab(AboutHomeTabs tab) {
         mSolo.clickOnText(tab.toString().replace("_", " "));
     }
 
     /**
      * Swipes to an about:home tab.
-     * @param int swipeVector Value and direction to swipe (go left for negative, right for positive).
+     * @param swipeVector swipeVector Value and direction to swipe (go left for negative, right for positive).
      */
     private void swipeAboutHome(int swipeVector) {
         // Increase swipe width, which will especially impact tablets.
         int swipeWidth = mDriver.getGeckoWidth() - 1;
         int swipeHeight = mDriver.getGeckoHeight() / 2;
 
         if (swipeVector >= 0) {
             // Emulate swipe motion from right to left.
@@ -227,18 +228,16 @@ abstract class AboutHomeTest extends Pix
                 mActions.drag(0, swipeWidth, swipeHeight, swipeHeight);
                 mSolo.sleep(100);
             }
         }
     }
 
     /**
      * This method can be used to open the different tabs of about:home.
-     *
-     * @param AboutHomeTabs enum item
      */
     protected void openAboutHomeTab(AboutHomeTabs tab) {
         focusUrlBar();
         ViewPager pager = mSolo.getView(ViewPager.class, 0);
         final int currentTabIndex = pager.getCurrentItem();
         int tabOffset;
 
         // Handle tablets by just clicking the visible tab title.
--- a/mobile/android/tests/browser/robocop/testAddSearchEngine.java
+++ b/mobile/android/tests/browser/robocop/testAddSearchEngine.java
@@ -152,17 +152,17 @@ public class testAddSearchEngine extends
                 }
 
                 SearchEngineBar searchEngineBar = (SearchEngineBar) mSolo.getView(R.id.search_engine_bar);
                 if (searchEngineBar == null || searchEngineBar.getAdapter() == null) {
                     return false;
                 }
 
                 final int actualCount = searchResultList.getAdapter().getCount()
-                        + searchEngineBar.getAdapter().getCount()
+                        + searchEngineBar.getAdapter().getItemCount()
                         - 1; // Subtract one for the search engine bar label (Bug 1172071)
 
                 return (actualCount == expectedCount);
             }
         }, MAX_WAIT_TEST_MS);
 
         // Exit about:home
         mSolo.goBack();
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -155,16 +155,19 @@ pref("dom.enable_performance", true);
 pref("dom.enable_resource_timing", true);
 
 // Enable high-resolution timing markers for users
 pref("dom.enable_user_timing", true);
 
 // Enable printing performance marks/measures to log
 pref("dom.performance.enable_user_timing_logging", false);
 
+// Enable notification of performance timing
+pref("dom.performance.enable_notify_performance_timing", false);
+
 // Whether the Gamepad API is enabled
 pref("dom.gamepad.enabled", true);
 #ifdef RELEASE_BUILD
 pref("dom.gamepad.non_standard_events.enabled", false);
 #else
 pref("dom.gamepad.non_standard_events.enabled", true);
 #endif
 
--- a/python/mozbuild/mozbuild/action/package_geckolibs_aar.py
+++ b/python/mozbuild/mozbuild/action/package_geckolibs_aar.py
@@ -87,54 +87,54 @@ def _generate_geckoview_classes_jar(dist
         with zipfile.ZipFile(f.path) as zf:
             zf.extractall(geckoview_aar_classes_path)
 
     # Rezip them into a single classes.jar file.
     classes_jar_path =  os.path.join(distdir, 'classes.jar')
     _zipdir(geckoview_aar_classes_path, classes_jar_path)
     return File(classes_jar_path)
 
-def package_geckolibs_aar(topsrcdir, distdir, output_file):
+def package_geckolibs_aar(topsrcdir, distdir, appname, output_file):
     jarrer = Jarrer(optimize=False)
 
     srcdir = os.path.join(topsrcdir, 'mobile', 'android', 'geckoview_library', 'geckolibs')
     jarrer.add('AndroidManifest.xml', File(os.path.join(srcdir, 'AndroidManifest.xml')))
     jarrer.add('classes.jar', File(os.path.join(srcdir, 'classes.jar')))
 
-    jni = FileFinder(os.path.join(distdir, 'fennec', 'lib'))
+    jni = FileFinder(os.path.join(distdir, appname, 'lib'))
     for p, f in jni.find('**/*.so'):
         jarrer.add(os.path.join('jni', p), f)
 
     # Include the buildinfo JSON as an asset, to give future consumers at least
     # a hope of determining where this AAR came from.
     json = FileFinder(distdir, ignore=['*.mozinfo.json'])
     for p, f in json.find('*.json'):
         jarrer.add(os.path.join('assets', p), f)
 
     # This neatly ignores omni.ja.
-    assets = FileFinder(os.path.join(distdir, 'fennec', 'assets'))
+    assets = FileFinder(os.path.join(distdir, appname, 'assets'))
     for p, f in assets.find('**/*.so'):
         jarrer.add(os.path.join('assets', p), f)
 
     jarrer.copy(output_file)
     return 0
 
-def package_geckoview_aar(topsrcdir, distdir, output_file):
+def package_geckoview_aar(topsrcdir, distdir, appname, output_file):
     jarrer = Jarrer(optimize=False)
-    fennec_path = os.path.join(distdir, 'fennec')
-    assets = FileFinder(os.path.join(fennec_path, 'assets'), ignore=['*.so'])
+    app_path = os.path.join(distdir, appname)
+    assets = FileFinder(os.path.join(app_path, 'assets'), ignore=['*.so'])
     for p, f in assets.find('omni.ja'):
         jarrer.add(os.path.join('assets', p), f)
 
     # The folder that contains Fennec's JAR files and resources.
     base_path = os.path.join(distdir, '..', 'mobile', 'android', 'base')
 
     # The resource set is packaged during Fennec's build.
     resjar = JarReader(os.path.join(base_path, 'geckoview_resources.zip'))
-    for p, f in JarFinder(p, resjar).find('*'):
+    for p, f in JarFinder(base_path, resjar).find('*'):
         jarrer.add(os.path.join('res', p), f)
 
     # Package the contents of all Fennec JAR files into classes.jar.
     classes_jar_file = _generate_geckoview_classes_jar(distdir, base_path)
     jarrer.add('classes.jar', classes_jar_file)
 
     # Add R.txt.
     jarrer.add('R.txt', File(os.path.join(base_path, 'R.txt')))
@@ -154,33 +154,35 @@ def main(args):
     parser.add_argument('--verbose', '-v', default=False, action='store_true',
                         help='be verbose')
     parser.add_argument('--revision',
                         help='Revision identifier to write.')
     parser.add_argument('--topsrcdir',
                         help='Top source directory.')
     parser.add_argument('--distdir',
                         help='Distribution directory (usually $OBJDIR/dist).')
+    parser.add_argument('--appname',
+                        help='Application name (usually $MOZ_APP_NAME, like "fennec").')
     args = parser.parse_args(args)
 
     # An Ivy 'publication' date must be given in the form yyyyMMddHHmmss, and Mozilla buildids are in this format.
     if len(args.revision) != 14:
         raise ValueError('Revision must be in yyyyMMddHHmmss format: %s' % args.revision)
 
     paths_to_hash = []
 
     groupId='org.mozilla'
     packaging_type='aar'
     gecklibs_aar = os.path.join(args.dir, 'geckolibs-{revision}.aar').format(revision=args.revision)
     paths_to_hash.append(gecklibs_aar)
     geckoview_aar = os.path.join(args.dir, 'geckoview-{revision}.aar').format(revision=args.revision)
     paths_to_hash.append(geckoview_aar)
 
-    package_geckolibs_aar(args.topsrcdir, args.distdir, gecklibs_aar)
-    package_geckoview_aar(args.topsrcdir, args.distdir, geckoview_aar)
+    package_geckolibs_aar(args.topsrcdir, args.distdir, args.appname, gecklibs_aar)
+    package_geckoview_aar(args.topsrcdir, args.distdir, args.appname, geckoview_aar)
 
     geckolibs_pom_path = os.path.join(args.dir, 'geckolibs-{revision}.pom').format(revision=args.revision)
     paths_to_hash.append(geckolibs_pom_path)
     geckolibs_pom = MAVEN_POM_TEMPLATE.format(
             groupId=groupId,
             artifactId='geckolibs',
             version=args.revision,
             packaging=packaging_type,
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -201,19 +201,17 @@ class TaskCache(CacheManager):
         try:
             artifact_re = JOB_DETAILS[job]['re']
         except KeyError:
             self.log(logging.INFO, 'artifact',
                 {'job': job},
                 'Unknown job {job}')
             raise KeyError("Unknown job")
 
-        # Bug 1175655: it appears that the Task Cluster index only takes
-        # 12-char hex hashes.
-        key = '{rev}.{tree}.{job}'.format(rev=rev[:12], tree=tree, job=job)
+        key = '{rev}.{tree}.{job}'.format(rev=rev, tree=tree, job=job)
         try:
             namespace = 'buildbot.revisions.{key}'.format(key=key)
             task = self._index.findTask(namespace)
         except Exception:
             # Not all revisions correspond to pushes that produce the job we
             # care about; and even those that do may not have completed yet.
             raise ValueError('Task for {key} does not exist (yet)!'.format(key=key))
         taskId = task['taskId']
@@ -350,23 +348,29 @@ class Artifacts(object):
         self.log(logging.INFO, 'artifact',
             {'revset': revset},
             'Installing from {revset}')
 
         url = None
         with self._task_cache as task_cache, self._pushhead_cache as pushhead_cache:
             # with blocks handle handle persistence.
             for pushhead in pushhead_cache.pushheads(self._tree, revset):
+                self.log(logging.DEBUG, 'artifact',
+                    {'pushhead': pushhead},
+                    'Trying to find artifacts for pushhead {pushhead}.')
                 try:
                     url = task_cache.artifact_url(self._tree, self._job, pushhead)
                     break
                 except ValueError:
                     pass
         if url:
             return self.install_from_url(url, distdir)
+        self.log(logging.ERROR, 'artifact',
+                 {'revset': revset},
+                 'No built artifacts for {revset} found.')
         return 1
 
     def install_from(self, source, distdir):
         if source and os.path.isfile(source):
             return self.install_from_file(source, distdir)
         elif source and urlparse.urlparse(source).scheme:
             return self.install_from_url(source, distdir)
         else:
--- a/testing/mozharness/mozharness.json
+++ b/testing/mozharness/mozharness.json
@@ -1,4 +1,4 @@
 {
     "repo": "https://hg.mozilla.org/build/mozharness",
-    "revision": "d72df953784c"
+    "revision": "9e3b69e89620"
 }
--- a/testing/mozharness/mozharness/mozilla/gaia.py
+++ b/testing/mozharness/mozharness/mozilla/gaia.py
@@ -376,17 +376,17 @@ class GaiaMixin(object):
                'node_modules',
                'NODE_MODULES_SRC=npm-cache',
                'VIRTUALENV_EXISTS=1']
         kwargs = {
             'output_timeout': 300,
             'error_list': self.npm_error_list
         }
         code = self.retry(self.run_command, attempts=3, good_statuses=(0,),
-                          args=[cmd, dirs['abs_gaia_dir']], kwargs=kwargs)
+                          args=[cmd, dirs['abs_gaia_dir']], cleanup=cleanup_node_modules, kwargs=kwargs)
         if code:
             # Dump npm-debug.log, if it exists
             npm_debug = os.path.join(dirs['abs_gaia_dir'], 'npm-debug.log')
             if os.access(npm_debug, os.F_OK):
                 self.info('dumping npm-debug.log')
                 self.run_command(['cat', npm_debug])
             else:
                 self.info('npm-debug.log doesn\'t exist, not dumping')
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -1065,17 +1065,17 @@ var LoginUtils = {
 function UserAutoCompleteResult (aSearchString, matchingLogins) {
   function loginSort(a,b) {
     var userA = a.username.toLowerCase();
     var userB = b.username.toLowerCase();
 
     if (userA < userB)
       return -1;
 
-    if (userB > userA)
+    if (userA > userB)
       return  1;
 
     return 0;
   };
 
   this.searchString = aSearchString;
   this.logins = matchingLogins.sort(loginSort);
   this.matchCount = matchingLogins.length;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7871,17 +7871,17 @@
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether or not a session has tracking protection enabled"
   },
   "TRACKING_PROTECTION_SHIELD": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 4,
-    "description": "Tracking protection shield (0 = not shown, 1 = loaded, 2 = blocked, 3 = due to mixed content"
+    "description": "Tracking protection shield (0 = not shown, 1 = loaded, 2 = blocked)"
   },
   "TRACKING_PROTECTION_EVENTS": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 3,
     "description": "Doorhanger shown = 0, Disable = 1, Enable = 2"
   },
   "SERVICE_WORKER_REGISTRATION_LOADING": {
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -118,16 +118,17 @@
       <li><a href="about:license#openvision">OpenVision License</a></li>
       <li><a href="about:license#pbkdf2-sha256">pbkdf2_sha256 License</a></li>
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
       <li><a href="about:license#pocketsphinx">Pocketsphinx License</a></li>
 #endif
       <li><a href="about:license#praton">praton License</a></li>
       <li><a href="about:license#qcms">qcms License</a></li>
       <li><a href="about:license#qrcode-generator">QR Code Generator License</a></li>
+      <li><a href="about:license#react">React License</a></li>
       <li><a href="about:license#xdg">Red Hat xdg_user_dir_lookup License</a></li>
       <li><a href="about:license#hunspell-ru">Russian Spellchecking Dictionary License</a></li>
       <li><a href="about:license#sctp">SCTP Licenses</a></li>
       <li><a href="about:license#skia">Skia License</a></li>
       <li><a href="about:license#snappy">Snappy License</a></li>
 #ifdef USE_STLPORT
       <li><a href="about:license#stlport">STLport License</a></li>
 #endif
@@ -3739,16 +3740,52 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE F
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 </pre>
 
 
     <hr>
 
+    <h1><a id="react"></a>React License</h1>
+
+    <p>This license applies to various files in the Mozilla codebase.</p>
+
+<pre>
+Copyright (c) 2013-2015, Facebook, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name Facebook nor the names of its contributors may be used to
+   endorse or promote products derived from this software without specific
+   prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+    <hr>
+
     <h1><a id="xdg"></a>Red Hat xdg_user_dir_lookup License</h1>
 
     <p>This license applies to the
     <span class="path">xdg_user_dir_lookup</span> function in
     <span class="path">xpcom/io/SpecialSystemDirectory.cpp</span>.</p>
 
 <pre>
 Copyright (c) 2007 Red Hat, Inc.
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -724,20 +724,23 @@
         </getter>
       </property>
 
       <property name="selected" readonly="true"
                 onget="return this.getAttribute('selected') == 'true';"/>
 
       <property name="_selected">
         <setter><![CDATA[
-          if (val)
+          if (val) {
             this.setAttribute("selected", "true");
-          else
+            this.setAttribute("visuallyselected", "true");
+          } else {
             this.removeAttribute("selected");
+            this.removeAttribute("visuallyselected");
+          }
 
           this._setPositionAttributes(val);
 
           return val;
         ]]></setter>
       </property>
 
       <method name="_setPositionAttributes">
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/performance-entries.js
@@ -0,0 +1,83 @@
+/* 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/. */
+
+/**
+ * The performanceEntries actor emits events corresponding to performance
+ * entries. It receives `performanceentry` events containing the performance
+ * entry details and emits an event containing the name, type, origin, and
+ * epoch of the performance entry.
+ */
+
+const {
+  method, Arg, Option, RetVal, Front, FrontClass, Actor, ActorClass
+} = require("devtools/server/protocol");
+const events = require("sdk/event/core");
+
+let PerformanceEntriesActor = exports.PerformanceEntriesActor = ActorClass({
+
+  typeName: "performanceEntries",
+
+  listenerAdded: false,
+
+  events: {
+    "entry" : {
+      type: "entry",
+      detail: Arg(0, "json") // object containing performance entry name, type,
+                             // origin, and epoch.
+    }
+  },
+
+  initialize: function(conn, tabActor) {
+    Actor.prototype.initialize.call(this, conn);
+    this.window = tabActor.window;
+  },
+
+  /**
+   * Start tracking the user timings.
+   */
+  start: method(function() {
+    if (!this.listenerAdded) {
+      this.onPerformanceEntry = this.onPerformanceEntry.bind(this);
+      this.window.addEventListener("performanceentry", this.onPerformanceEntry, true);
+      this.listenerAdded = true;
+    }
+  }),
+
+  /**
+   * Stop tracking the user timings.
+   */
+  stop: method(function() {
+    if (this.listenerAdded) {
+      this.window.removeEventListener("performanceentry", this.onPerformanceEntry, true);
+      this.listenerAdded = false;
+    }
+  }),
+
+  disconnect: function() {
+    this.destroy();
+  },
+
+  destroy: function() {
+    this.stop();
+    Actor.prototype.destroy.call(this);
+  },
+
+  onPerformanceEntry: function (e) {
+    let emitDetail = {
+      type: e.entryType,
+      name: e.name,
+      origin: e.origin,
+      epoch: e.epoch
+    };
+    events.emit(this, 'entry', emitDetail);
+  }
+});
+
+exports.PerformanceEntriesFront = FrontClass(PerformanceEntriesActor, {
+  initialize: function(client, form) {
+    Front.prototype.initialize.call(this, client);
+    this.actorID = form.performanceEntriesActor;
+    this.manage(this);
+  },
+});
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2287,27 +2287,65 @@ SourceActor.prototype = {
           return;
         }
 
         this._addonPath = path;
       }
     }
   },
 
+  _reportLoadSourceError: function (error, map=null) {
+    try {
+      DevToolsUtils.reportException("SourceActor", error);
+
+      JSON.stringify(this.form(), null, 4).split(/\n/g)
+        .forEach(line => console.error("\t", line));
+
+      if (!map) {
+        return;
+      }
+
+      console.error("\t", "source map's sourceRoot =", map.sourceRoot);
+
+      console.error("\t", "source map's sources =");
+      map.sources.forEach(s => {
+        let hasSourceContent = map.sourceContentFor(s, true);
+        console.error("\t\t", s, "\t",
+                      hasSourceContent ? "has source content" : "no source content");
+      });
+
+      console.error("\t", "source map's sourcesContent =");
+      map.sourcesContent.forEach(c => {
+        if (c.length > 80) {
+          c = c.slice(0, 77) + "...";
+        }
+        c = c.replace(/\n/g, "\\n");
+        console.error("\t\t", c);
+      });
+    } catch (e) { }
+  },
+
   _getSourceText: function () {
     let toResolvedContent = t => ({
       content: t,
       contentType: this._contentType
     });
 
     let genSource = this.generatedSource || this.source;
     return this.threadActor.sources.fetchSourceMap(genSource).then(map => {
-      let sc;
-      if (map && (sc = map.sourceContentFor(this.url))) {
-        return toResolvedContent(sc);
+      if (map) {
+        try {
+          let sourceContent = map.sourceContentFor(this.url);
+          if (sourceContent) {
+            return toResolvedContent(sourceContent);
+          }
+        } catch (error) {
+          this._reportLoadSourceError(error, map);
+          throw error;
+        }
       }
 
       // Use `source.text` if it exists, is not the "no source"
       // string, and the content type of the source is JavaScript. It
       // will be "no source" if the Debugger API wasn't able to load
       // the source because sources were discarded
       // (javascript.options.discardSystemSource == true). Re-fetch
       // non-JS sources to get the contentType from the headers.
@@ -2322,20 +2360,24 @@ SourceActor.prototype = {
         // there are inline sources). Otherwise, we can't trust the
         // cache because we are most likely here because we are
         // fetching the original text for sourcemapped code, and the
         // page hasn't requested it before (if it has, it was a
         // previous debugging session).
         let sourceFetched = fetch(this.url, { loadFromCache: this.isInlineSource });
 
         // Record the contentType we just learned during fetching
-        return sourceFetched.then(result => {
-          this._contentType = result.contentType;
-          return result;
-        });
+        return sourceFetched
+          .then(result => {
+            this._contentType = result.contentType;
+            return result;
+          }, error => {
+            this._reportLoadSourceError(error, map);
+            throw error;
+          });
       }
     });
   },
 
   /**
    * Get all executable lines from the current source
    * @return Array - Executable lines of the current script
    **/
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -540,16 +540,21 @@ var DebuggerServer = {
       constructor: "AnimationsActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/promises", {
       prefix: "promises",
       constructor: "PromisesActor",
       type: { global: true, tab: true }
     });
+    this.registerModule("devtools/server/actors/performance-entries", {
+      prefix: "performanceEntries",
+      constructor: "PerformanceEntriesActor",
+      type: { tab: true }
+    });
   },
 
   /**
    * Passes a set of options to the BrowserAddonActors for the given ID.
    *
    * @param aId string
    *        The ID of the add-on to pass the options to
    * @param aOptions object
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -70,16 +70,17 @@ EXTRA_JS_MODULES.devtools.server.actors 
     'actors/framerate.js',
     'actors/gcli.js',
     'actors/highlighter.js',
     'actors/inspector.js',
     'actors/layout.js',
     'actors/memory.js',
     'actors/monitor.js',
     'actors/object.js',
+    'actors/performance-entries.js',
     'actors/preference.js',
     'actors/pretty-print-worker.js',
     'actors/profiler.js',
     'actors/promises.js',
     'actors/root.js',
     'actors/script.js',
     'actors/settings.js',
     'actors/storage.js',
--- a/toolkit/devtools/shared/memory.js
+++ b/toolkit/devtools/shared/memory.js
@@ -90,44 +90,52 @@ let Memory = exports.Memory = Class({
    * Gets the current MemoryBridge attach/detach state.
    */
   getState: function () {
     return this.state;
   },
 
   _clearDebuggees: function() {
     if (this._dbg) {
-      if (this.dbg.memory.trackingAllocationSites) {
+      if (this.isRecordingAllocations()) {
         this.dbg.memory.drainAllocationsLog();
       }
       this._clearFrames();
       this.dbg.removeAllDebuggees();
     }
   },
 
   _clearFrames: function() {
-    if (this.dbg.memory.trackingAllocationSites) {
+    if (this.isRecordingAllocations()) {
       this._frameCache.clearFrames();
     }
   },
 
   /**
    * Handler for the parent actor's "window-ready" event.
    */
   _onWindowReady: function({ isTopLevel }) {
     if (this.state == "attached") {
-      if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
+      if (isTopLevel && this.isRecordingAllocations()) {
         this._clearDebuggees();
         this._frameCache.initFrames();
       }
       this.dbg.addDebuggees();
     }
   },
 
   /**
+   * Returns a boolean indicating whether or not allocation
+   * sites are being tracked.
+   */
+  isRecordingAllocations: function () {
+    return this.dbg.memory.trackingAllocationSites;
+  },
+
+  /**
    * Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
    * more information.
    */
   takeCensus: expectState("attached", function() {
     return this.dbg.memory.takeCensus();
   }, `taking census`),
 
   /**
@@ -141,18 +149,18 @@ let Memory = exports.Memory = Class({
    *                 log. If new allocs occur while at capacity, oldest allocs are lost.
    *                 Must fit in a 32 bit signed integer.
    * @param {number} options.drainAllocationsTimeout
    *                 A number in milliseconds of how often, at least, an `allocation` event
    *                 gets emitted (and drained), and also emits and drains on every GC event,
    *                 resetting the timer.
    */
   startRecordingAllocations: expectState("attached", function(options = {}) {
-    if (this.dbg.memory.trackingAllocationSites) {
-      return Date.now();
+    if (this.isRecordingAllocations()) {
+      return this._getCurrentTime();
     }
 
     this._frameCache.initFrames();
 
     this.dbg.memory.allocationSamplingProbability = options.probability != null
       ? options.probability
       : 1.0;
 
@@ -166,32 +174,35 @@ let Memory = exports.Memory = Class({
       this._poller.arm();
     }
 
     if (options.maxLogLength != null) {
       this.dbg.memory.maxAllocationsLogLength = options.maxLogLength;
     }
     this.dbg.memory.trackingAllocationSites = true;
 
-    return Date.now();
+    return this._getCurrentTime();
   }, `starting recording allocations`),
 
   /**
    * Stop recording allocation sites.
    */
   stopRecordingAllocations: expectState("attached", function() {
+    if (!this.isRecordingAllocations()) {
+      return this._getCurrentTime();
+    }
     this.dbg.memory.trackingAllocationSites = false;
     this._clearFrames();
 
     if (this._poller) {
       this._poller.disarm();
       this._poller = null;
     }
 
-    return Date.now();
+    return this._getCurrentTime();
   }, `stopping recording allocations`),
 
   /**
    * Return settings used in `startRecordingAllocations` for `probability`
    * and `maxLogLength`. Currently only uses in tests.
    */
   getAllocationsSettings: expectState("attached", function() {
     return {
@@ -375,9 +386,17 @@ let Memory = exports.Memory = Class({
    * Called on `drainAllocationsTimeoutTimer` interval if and only if set during `startRecordingAllocations`,
    * or on a garbage collection event if drainAllocationsTimeout was set.
    * Drains allocation log and emits as an event and restarts the timer.
    */
   _emitAllocations: function () {
     events.emit(this, "allocations", this.getAllocations());
     this._poller.arm();
   },
+
+  /**
+   * Accesses the docshell to return the current process time.
+   */
+  _getCurrentTime: function () {
+    return (this.parent.isRootActor ? this.parent.docShell : this.parent.originalDocShell).now();
+  },
+
 });
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -2508,23 +2508,23 @@ this.XPIProvider = {
         };
 
         for (let addon of addons) {
           // The add-on might have vanished, we'll catch that on the next startup
           if (!addon._sourceBundle.exists())
             continue;
 
           let signedState = yield verifyBundleSignedState(addon._sourceBundle, addon);
-          if (signedState == addon.signedState)
-            continue;
-
-          addon.signedState = signedState;
-          AddonManagerPrivate.callAddonListeners("onPropertyChanged",
-                                                 createWrapper(addon),
-                                                 ["signedState"]);
+
+          if (signedState != addon.signedState) {
+            addon.signedState = signedState;
+            AddonManagerPrivate.callAddonListeners("onPropertyChanged",
+                                                   createWrapper(addon),
+                                                   ["signedState"]);
+          }
 
           let disabled = XPIProvider.updateAddonDisabledState(addon);
           if (disabled !== undefined)
             changes[disabled ? "disabled" : "enabled"].push(addon.id);
         }
 
         XPIDatabase.saveChanges();
 
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -337,32 +337,36 @@ function DBAddonInternal(aLoaded) {
       return null;
     });
 }
 
 function DBAddonInternalPrototype()
 {
   this.applyCompatibilityUpdate =
     function(aUpdate, aSyncCompatibility) {
+      let wasCompatible = this.isCompatible;
+
       this.targetApplications.forEach(function(aTargetApp) {
         aUpdate.targetApplications.forEach(function(aUpdateTarget) {
           if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
               Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
             aTargetApp.minVersion = aUpdateTarget.minVersion;
             aTargetApp.maxVersion = aUpdateTarget.maxVersion;
             XPIDatabase.saveChanges();
           }
         });
       });
       if (aUpdate.multiprocessCompatible !== undefined &&
           aUpdate.multiprocessCompatible != this.multiprocessCompatible) {
         this.multiprocessCompatible = aUpdate.multiprocessCompatible;
         XPIDatabase.saveChanges();
       }
-      XPIProvider.updateAddonDisabledState(this);
+
+      if (wasCompatible != this.isCompatible)
+        XPIProvider.updateAddonDisabledState(this);
     };
 
   this.toJSON =
     function() {
       return copyProperties(this, PROP_JSON_FIELDS);
     };
 
   Object.defineProperty(this, "inDatabase",
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_updatepref.js
@@ -0,0 +1,134 @@
+// Disable update security
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+const DATA = "data/signing_checks/";
+const ID = "test@tests.mozilla.org";
+
+Components.utils.import("resource://testing-common/httpd.js");
+var gServer = new HttpServer();
+gServer.start();
+
+gServer.registerPathHandler("/update.rdf", function(request, response) {
+  let updateData = {};
+  updateData[ID] = [{
+    version: "2.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "4",
+      maxVersion: "6"
+    }]
+  }];
+
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.write(createUpdateRDF(updateData));
+});
+
+const SERVER = "127.0.0.1:" + gServer.identity.primaryPort;
+Services.prefs.setCharPref("extensions.update.background.url", "http://" + SERVER + "/update.rdf");
+
+function verifySignatures() {
+  return new Promise(resolve => {
+    let observer = (subject, topic, data) => {
+      Services.obs.removeObserver(observer, "xpi-signature-changed");
+      resolve(JSON.parse(data));
+    }
+    Services.obs.addObserver(observer, "xpi-signature-changed", false);
+
+    do_print("Verifying signatures");
+    let XPIscope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
+    XPIscope.XPIProvider.verifySignatures();
+  });
+}
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
+
+  // Start and stop the manager to initialise everything in the profile before
+  // actual testing
+  startupManager();
+  shutdownManager();
+
+  run_next_test();
+}
+
+// Updating the pref without changing the app version won't disable add-ons
+// immediately but will after a signing check
+add_task(function*() {