Merge m-c to inbound on a CLOSED TREE. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 16 Jun 2014 14:26:54 -0400
changeset 188918 f19ca5123d6a548af649f13044d2161a10f70578
parent 188872 32c13ea4c9e4f1b52fda244730d8c7894df436c6 (current diff)
parent 188917 156a0570e8f4f58c593d84405623d0badbb49188 (diff)
child 188919 ff0404eba1963477a33ce980caad29a556fb410e
child 189002 bb35d1b7363494e7f995a09a43e7f63efd489269
child 189008 a533027d8cff928740325282c262244ff5922275
child 189052 f963ee82e59aa8cced5b68dfb217fa5fc150a4a8
push id44938
push userryanvm@gmail.com
push dateMon, 16 Jun 2014 18:26:43 +0000
treeherdermozilla-inbound@f19ca5123d6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound on a CLOSED TREE. a=merge
browser/devtools/shared/test/browser_graphs-10.js
--- a/b2g/chrome/content/devtools.js
+++ b/b2g/chrome/content/devtools.js
@@ -503,16 +503,18 @@ developerHUD.registerWatcher(eventLoopLa
  * The Memory Watcher uses devtools actors to track memory usage.
  */
 let memoryWatcher = {
 
   _client: null,
   _fronts: new Map(),
   _timers: new Map(),
   _watching: {
+    uss: false,
+    appmemory: false,
     jsobjects: false,
     jsstrings: false,
     jsother: false,
     dom: false,
     style: false,
     other: false
   },
   _active: false,
@@ -520,70 +522,82 @@ let memoryWatcher = {
   init: function mw_init(client) {
     this._client = client;
     let watching = this._watching;
 
     for (let key in watching) {
       let category = key;
       SettingsListener.observe('hud.' + category, false, watch => {
         watching[category] = watch;
+        this.update();
       });
     }
+  },
 
-    SettingsListener.observe('hud.appmemory', false, enabled => {
-      if (this._active = enabled) {
-        for (let target of this._fronts.keys()) {
-          this.measure(target);
-        }
-      } else {
-        for (let target of this._fronts.keys()) {
-          clearTimeout(this._timers.get(target));
-          target.clear({name: 'memory'});
-        }
+  update: function mw_update() {
+    let watching = this._watching;
+    let active = watching.memory || watching.uss;
+
+    if (this._active) {
+      for (let target of this._fronts.keys()) {
+        if (!watching.appmemory) target.clear({name: 'memory'});
+        if (!watching.uss) target.clear({name: 'uss'});
+        if (!active) clearTimeout(this._timers.get(target));
       }
-    });
+    } else if (active) {
+      for (let target of this._fronts.keys()) {
+        this.measure(target);
+      }
+    }
+    this._active = active;
   },
 
   measure: function mw_measure(target) {
-
-    // TODO Also track USS (bug #976024).
-
     let watch = this._watching;
     let front = this._fronts.get(target);
 
-    front.measure().then((data) => {
+    if (watch.uss) {
+      front.residentUnique().then(value => {
+        target.update({name: 'uss', value: value});
+      }, err => {
+        console.error(err);
+      });
+    }
 
-      let total = 0;
-      if (watch.jsobjects) {
-        total += parseInt(data.jsObjectsSize);
-      }
-      if (watch.jsstrings) {
-        total += parseInt(data.jsStringsSize);
-      }
-      if (watch.jsother) {
-        total += parseInt(data.jsOtherSize);
-      }
-      if (watch.dom) {
-        total += parseInt(data.domSize);
-      }
-      if (watch.style) {
-        total += parseInt(data.styleSize);
-      }
-      if (watch.other) {
-        total += parseInt(data.otherSize);
-      }
-      // TODO Also count images size (bug #976007).
+    if (watch.appmemory) {
+      front.measure().then(data => {
+        let total = 0;
+        if (watch.jsobjects) {
+          total += parseInt(data.jsObjectsSize);
+        }
+        if (watch.jsstrings) {
+          total += parseInt(data.jsStringsSize);
+        }
+        if (watch.jsother) {
+          total += parseInt(data.jsOtherSize);
+        }
+        if (watch.dom) {
+          total += parseInt(data.domSize);
+        }
+        if (watch.style) {
+          total += parseInt(data.styleSize);
+        }
+        if (watch.other) {
+          total += parseInt(data.otherSize);
+        }
+        // TODO Also count images size (bug #976007).
 
-      target.update({name: 'memory', value: total});
-      let duration = parseInt(data.jsMilliseconds) + parseInt(data.nonJSMilliseconds);
-      let timer = setTimeout(() => this.measure(target), 100 * duration);
-      this._timers.set(target, timer);
-    }, (err) => {
-      console.error(err);
-    });
+        target.update({name: 'memory', value: total});
+      }, err => {
+        console.error(err);
+      });
+    }
+
+    let timer = setTimeout(() => this.measure(target), 300);
+    this._timers.set(target, timer);
   },
 
   trackTarget: function mw_trackTarget(target) {
     target.register('uss');
     target.register('memory');
     this._fronts.set(target, MemoryFront(this._client, target.actor));
     if (this._active) {
       this.measure(target);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="eb0c93761bb9919567257d19bf25fa433cda3c00"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <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="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="eb0c93761bb9919567257d19bf25fa433cda3c00"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
@@ -124,17 +124,17 @@
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="f2914eacee9120680a41463708bb6ee8291749fc"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="81c4a859d75d413ad688067829d21b7ba9205f81"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="f0689ac1914cdbc59e53bdc9edd9013dc157c299"/>
   <project name="platform/external/bluetooth/glib" path="external/bluetooth/glib" revision="dd925f76e4f149c3d5571b80e12f7e24bbe89c59"/>
   <project name="platform/external/dbus" path="external/dbus" revision="ea87119c843116340f5df1d94eaf8275e1055ae8"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="494c177966fdc31183a5f7af82dc9130f523da4b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="320b05a5761eb2a4816f7529c91ea49422979b55"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="5b934dc57dae25f286b0e7210dc6ff47f3244927"/>
-  <project name="platform/frameworks/base" path="frameworks/base" revision="655ac07f6574d279c759c6983ed4389369a9c96c"/>
+  <project name="platform/frameworks/base" path="frameworks/base" revision="af3e4fbdc9369643a92015ea2657361f3b1b46fe"/>
   <project name="platform/frameworks/native" path="frameworks/native" revision="33a2b51f78416536e1bfba0c0b7776db307f3a4f"/>
   <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="484802559ed106bac4811bd01c024ca64f741e60"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="83f363a26069e9c188d2aaef5b9ef63e84ad1511"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="5e110615212302c5d798a3c223dcee458817651c"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="280d29203b2aa30d713c5a6cc63d626e5a7df822"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="5dc48bd46f9589653f8bf297be5d73676f2e2867"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "39d06bda5d404daff2bc09939a038c27d2fe8a81", 
+    "revision": "99d5a7c6f3b1d9c04dceda9b235bc5ff0c12b866", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <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/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="dfc4703bb81d1fa4f2087a1a6124b47a80a5d1de"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eac13407742a55b11e1877b4df2abdfd22cd582e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="27b2c2ef9a50d5dc79b6a771b3a3c775a888d13b"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c9cc24ebbfa5427defcc4c99d4d08c2ee6cbcea8"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/components/translation/BingTranslator.jsm
+++ b/browser/components/translation/BingTranslator.jsm
@@ -41,16 +41,17 @@ const MAX_REQUESTS = 15;
  *                             task is finished.
  */
 this.BingTranslation = function(translationDocument, sourceLanguage, targetLanguage) {
   this.translationDocument = translationDocument;
   this.sourceLanguage = sourceLanguage;
   this.targetLanguage = targetLanguage;
   this._pendingRequests = 0;
   this._partialSuccess = false;
+  this._serviceUnavailable = false;
   this._translatedCharacterCount = 0;
 };
 
 this.BingTranslation.prototype = {
   /**
    * Performs the translation, splitting the document into several chunks
    * respecting the data limits of the API.
    *
@@ -75,59 +76,87 @@ this.BingTranslation.prototype = {
         let request = this._generateNextTranslationRequest(currentIndex);
 
         // Create a real request to the server, and put it on the
         // pending requests list.
         let bingRequest = new BingRequest(request.data,
                                           this.sourceLanguage,
                                           this.targetLanguage);
         this._pendingRequests++;
-        bingRequest.fireRequest().then(this._chunkCompleted.bind(this));
+        bingRequest.fireRequest().then(this._chunkCompleted.bind(this),
+                                       this._chunkFailed.bind(this));
 
         currentIndex = request.lastIndex;
         if (request.finished) {
           break;
         }
       }
 
       return this._onFinishedDeferred.promise;
     }.bind(this));
   },
 
   /**
-   * Function called when a request sent to the server is completed.
-   * This function handles determining if the response was successful or not,
-   * calling the function to parse the result, and resolving the promise
-   * returned by the public `translate()` method when all chunks are completed.
+   * Function called when a request sent to the server completed successfully.
+   * This function handles calling the function to parse the result and the
+   * function to resolve the promise returned by the public `translate()`
+   * method when there's no pending request left.
    *
    * @param   request   The BingRequest sent to the server.
    */
   _chunkCompleted: function(bingRequest) {
-     this._pendingRequests--;
-     if (bingRequest.requestSucceeded &&
-         this._parseChunkResult(bingRequest)) {
-       // error on request
-       this._partialSuccess = true;
-       // Count the number of characters successfully translated.
-       this._translatedCharacterCount += bingRequest.characterCount;
-     }
+    if (this._parseChunkResult(bingRequest)) {
+      this._partialSuccess = true;
+      // Count the number of characters successfully translated.
+      this._translatedCharacterCount += bingRequest.characterCount;
+    }
+
+    this._checkIfFinished();
+  },
 
+  /**
+   * Function called when a request sent to the server has failed.
+   * This function handles deciding if the error is transient or means the
+   * service is unavailable (zero balance on the key) and calling the
+   * function to resolve the promise returned by the public `translate()`
+   * method when there's no pending request left.
+   *
+   * @param   aError   [optional] The RESTRequest that failed.
+   */
+  _chunkFailed: function(aError) {
+    if (aError instanceof RESTRequest &&
+        aError.response.status == 400) {
+      let body = aError.response.body;
+      if (body.contains("TranslateApiException") && body.contains("balance"))
+        this._serviceUnavailable = true;
+    }
+
+    this._checkIfFinished();
+  },
+
+  /**
+   * Function called when a request sent to the server has completed.
+   * This function handles resolving the promise
+   * returned by the public `translate()` method when all chunks are completed.
+   */
+  _checkIfFinished: function() {
     // Check if all pending requests have been
     // completed and then resolves the promise.
     // If at least one chunk was successful, the
     // promise will be resolved positively which will
     // display the "Success" state for the infobar. Otherwise,
     // the "Error" state will appear.
-    if (this._pendingRequests == 0) {
+    if (--this._pendingRequests == 0) {
       if (this._partialSuccess) {
         this._onFinishedDeferred.resolve({
           characterCount: this._translatedCharacterCount
         });
       } else {
-        this._onFinishedDeferred.reject("failure");
+        let error = this._serviceUnavailable ? "unavailable" : "failure";
+        this._onFinishedDeferred.reject(error);
       }
     }
   },
 
   /**
    * This function parses the result returned by Bing's Http.svc API,
    * which is a XML file that contains a number of elements. To our
    * particular interest, the only part of the response that matters
@@ -275,34 +304,26 @@ BingRequest.prototype = {
       requestString += '</Texts>' +
           '<To>' + this.targetLanguage + '</To>' +
         '</TranslateArrayRequest>';
 
       let utf8 = CommonUtils.encodeUTF8(requestString);
 
       let deferred = Promise.defer();
       request.post(utf8, function(err) {
+        if (request.error || !request.response.success)
+          deferred.reject(request);
+
         deferred.resolve(this);
       }.bind(this));
 
       this.networkRequest = request;
       return deferred.promise;
     }.bind(this));
-  },
-
-  /**
-   * Checks if the request succeeded. Only valid
-   * after the request has finished.
-   *
-   * @returns    True if the request succeeded.
-   */
-  get requestSucceeded() {
-    return !this.networkRequest.error &&
-            this.networkRequest.response.success;
-   }
+  }
 };
 
 /**
  * Authentication Token manager for the API
  */
 let BingTokenManager = {
   _currentToken: null,
   _currentExpiryTime: 0,
@@ -354,16 +375,22 @@ let BingTokenManager = {
       BingTokenManager._pendingRequest = null;
 
       if (err) {
         deferred.reject(err);
       }
 
       try {
         let json = JSON.parse(this.response.body);
+
+        if (json.error) {
+          deferred.reject(json.error);
+          return;
+        }
+
         let token = json.access_token;
         let expires_in = json.expires_in;
         BingTokenManager._currentToken = token;
         BingTokenManager._currentExpiryTime = new Date(Date.now() + expires_in * 1000);
         deferred.resolve(token);
       } catch (e) {
         deferred.reject(e);
       }
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -23,16 +23,19 @@ const DAILY_LAST_TEXT_FIELD = {type: Met
 const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC};
 
 
 this.Translation = {
   STATE_OFFER: 0,
   STATE_TRANSLATING: 1,
   STATE_TRANSLATED: 2,
   STATE_ERROR: 3,
+  STATE_UNAVAILABLE: 4,
+
+  serviceUnavailable: false,
 
   supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
   supportedTargetLanguages: ["en", "pl", "tr", "vi"],
 
   _defaultTargetLanguage: "",
   get defaultTargetLanguage() {
     if (!this._defaultTargetLanguage) {
       this._defaultTargetLanguage = Cc["@mozilla.org/chrome/chrome-registry;1"]
@@ -166,16 +169,21 @@ TranslationUI.prototype = {
     let notificationBox = this.notificationBox;
     let notif = notificationBox.appendNotification("", "translation", null,
                                                    notificationBox.PRIORITY_INFO_HIGH);
     notif.init(this);
     return notif;
   },
 
   shouldShowInfoBar: function(aURI) {
+    // Never show the infobar automatically while the translation
+    // service is temporarily unavailable.
+    if (Translation.serviceUnavailable)
+      return false;
+
     // Check if we should never show the infobar for this language.
     let neverForLangs =
       Services.prefs.getCharPref("browser.translation.neverForLanguages");
     if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1)
       return false;
 
     // or if we should never show the infobar for this domain.
     let perms = Services.perms;
@@ -205,16 +213,19 @@ TranslationUI.prototype = {
         if (msg.data.success) {
           this.originalShown = false;
           this.state = Translation.STATE_TRANSLATED;
           this.showURLBarIcon();
 
           // Record the number of characters translated.
           TranslationHealthReport.recordTranslation(msg.data.from, msg.data.to,
                                                     msg.data.characterCount);
+        } else if (msg.data.unavailable) {
+          Translation.serviceUnavailable = true;
+          this.state = Translation.STATE_UNAVAILABLE;
         } else {
           this.state = Translation.STATE_ERROR;
         }
         break;
     }
   }
 };
 
--- a/browser/components/translation/TranslationContentHandler.jsm
+++ b/browser/components/translation/TranslationContentHandler.jsm
@@ -137,17 +137,20 @@ TranslationContentHandler.prototype = {
               from: msg.data.from,
               to: msg.data.to,
               success: true
             });
             translationDocument.showTranslation();
           },
           error => {
             translationDocument.translationError = true;
-            this.global.sendAsyncMessage("Translation:Finished", {success: false});
+            let data = {success: false};
+            if (error == "unavailable")
+              data.unavailable = true;
+            this.global.sendAsyncMessage("Translation:Finished", data);
           }
         );
         break;
       }
 
       case "Translation:ShowOriginal":
         this.global.content.translationDocument.showOriginal();
         break;
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -80,16 +80,22 @@
               <xul:label class="translate-infobar-element"
                          value="&translation.errorTranslating.label;"/>
               <xul:button class="translate-infobar-element"
                           label="&translation.tryAgain.button;"
                           anonid="tryAgain"
                           oncommand="document.getBindingParent(this).translate();"/>
             </xul:hbox>
 
+            <!-- unavailable -->
+            <xul:vbox class="translation-unavailable" pack="center">
+              <xul:label class="translate-infobar-element"
+                         value="&translation.serviceUnavailable.label;"/>
+            </xul:vbox>
+
           </xul:deck>
           <xul:spacer flex="1"/>
 
           <xul:button type="menu"
                       class="translate-infobar-element options-menu-button"
                       anonid="options"
                       label="&translation.options.menu;">
             <xul:menupopup onpopupshowing="document.getBindingParent(this).optionsShowing();">
@@ -141,16 +147,21 @@
           ]]>
         </setter>
       </property>
 
       <method name="init">
         <parameter name="aTranslation"/>
         <body>
           <![CDATA[
+            if (Translation.serviceUnavailable) {
+              this.state = Translation.STATE_UNAVAILABLE;
+              return;
+            }
+
             this.translation = aTranslation;
             let bundle = Cc["@mozilla.org/intl/stringbundle;1"]
                            .getService(Ci.nsIStringBundleService)
                            .createBundle("chrome://global/locale/languageNames.properties");
 
             // Fill the lists of supported source languages.
             let detectedLanguage = this._getAnonElt("detectedLanguage");
             let fromLanguage = this._getAnonElt("fromLanguage");
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -496,22 +496,24 @@ let gDevToolsBrowser = {
     {
       toolbox.fireCustomKey(toolId);
 
       if (toolDefinition.preventClosingOnKey || toolbox.hostType == devtools.Toolbox.HostType.WINDOW) {
         toolbox.raise();
       } else {
         toolbox.destroy();
       }
+      gDevTools.emit("select-tool-command", toolId);
     } else {
       gDevTools.showToolbox(target, toolId).then(() => {
         let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
         let toolbox = gDevTools.getToolbox(target);
 
         toolbox.fireCustomKey(toolId);
+        gDevTools.emit("select-tool-command", toolId);
       });
     }
   },
 
   /**
    * Open a tab to allow connects to a remote browser
    */
   openConnectScreen: function(gBrowser) {
--- a/browser/devtools/framework/test/browser_devtools_api.js
+++ b/browser/devtools/framework/test/browser_devtools_api.js
@@ -33,21 +33,28 @@ function runTests(aTab) {
   is(gDevTools.getToolDefinitionMap().has(toolId), false,
     "The tool is not registered");
 
   gDevTools.registerTool(toolDefinition);
   is(gDevTools.getToolDefinitionMap().has(toolId), true,
     "The tool is registered");
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
+
   gDevTools.showToolbox(target, toolId).then(function(toolbox) {
+    // Wait for the test tool to be visible and selected.
+    let { promise: testToolShown, resolve } = promise.defer();
+    toolbox.once("test-tool-selected", resolve);
+
+    return testToolShown.then(() => toolbox);
+  }).then(function (toolbox) {
     is(toolbox.target, target, "toolbox target is correct");
     is(toolbox._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
     continueTests(toolbox);
-  }).then(null, console.error);
+  });
 }
 
 function continueTests(toolbox, panel) {
   ok(toolbox.getCurrentPanel(), "panel value is correct");
   is(toolbox.currentToolId, toolId, "toolbox _currentToolId is correct");
 
   ok(!toolbox.doc.getElementById("toolbox-tab-" + toolId).hasAttribute("icon-invertable"),
     "The tool tab does not have the invertable attribute");
@@ -60,23 +67,33 @@ function continueTests(toolbox, panel) {
 
   let toolDefinition = toolDefinitions.get(toolId);
   is(toolDefinition.id, toolId, "toolDefinition id is correct");
 
   gDevTools.unregisterTool(toolId);
   is(gDevTools.getToolDefinitionMap().has(toolId), false,
     "The tool is no longer registered");
 
+  // Wait for unregisterTool to select the next tool before
+  // attempting to destroy.
+  toolbox.on("select", function selectListener (_, id) {
+    if (id !== "test-tool") {
+      toolbox.off("select", selectListener);
+      destroyToolbox(toolbox);
+    }
+  });
+}
+
+function destroyToolbox(toolbox) {
   toolbox.destroy().then(function() {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     ok(gDevTools._toolboxes.get(target) == null, "gDevTools doesn't know about target");
     ok(toolbox._target == null, "toolbox doesn't know about target.");
-
     finishUp();
-  }).then(null, console.error);
+  });
 }
 
 function finishUp() {
   tempScope = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
--- a/browser/devtools/framework/test/browser_keybindings.js
+++ b/browser/devtools/framework/test/browser_keybindings.js
@@ -53,64 +53,64 @@ function test()
 
   function setupKeyBindingsTest()
   {
     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
       buildDevtoolsKeysetMap(win.document.getElementById("devtoolsKeyset"));
     }
 
     gDevTools.once("toolbox-ready", (e, toolbox) => {
-      inspectorShouldBeOpenAndHighlighting(toolbox.getCurrentPanel(), toolbox)
+      inspectorShouldBeOpenAndHighlighting(toolbox.getCurrentPanel(), toolbox);
     });
 
     keysetMap.inspector.synthesizeKey();
   }
 
   function inspectorShouldBeOpenAndHighlighting(aInspector, aToolbox)
   {
     is (aToolbox.currentToolId, "inspector", "Correct tool has been loaded");
 
     aToolbox.once("picker-started", () => {
       ok(true, "picker-started event received, highlighter started");
       keysetMap.inspector.synthesizeKey();
 
       aToolbox.once("picker-stopped", () => {
         ok(true, "picker-stopped event received, highlighter stopped");
-        aToolbox.once("webconsole-ready", (e, panel) => {
-          webconsoleShouldBeSelected(aToolbox, panel);
+        gDevTools.once("select-tool-command", () => {
+          webconsoleShouldBeSelected(aToolbox);
         });
         keysetMap.webconsole.synthesizeKey();
       });
     });
   }
 
-  function webconsoleShouldBeSelected(aToolbox, panel)
+  function webconsoleShouldBeSelected(aToolbox)
   {
-      is (aToolbox.currentToolId, "webconsole");
+      is (aToolbox.currentToolId, "webconsole", "webconsole should be selected.");
 
-      aToolbox.once("jsdebugger-ready", (e, panel) => {
-        jsdebuggerShouldBeSelected(aToolbox, panel);
+      gDevTools.once("select-tool-command", () => {
+        jsdebuggerShouldBeSelected(aToolbox);
       });
       keysetMap.jsdebugger.synthesizeKey();
   }
 
-  function jsdebuggerShouldBeSelected(aToolbox, panel)
+  function jsdebuggerShouldBeSelected(aToolbox)
   {
-      is (aToolbox.currentToolId, "jsdebugger");
+      is (aToolbox.currentToolId, "jsdebugger", "jsdebugger should be selected.");
 
-      aToolbox.once("netmonitor-ready", (e, panel) => {
-        netmonitorShouldBeSelected(aToolbox, panel);
+      gDevTools.once("select-tool-command", () => {
+        netmonitorShouldBeSelected(aToolbox);
       });
 
       keysetMap.netmonitor.synthesizeKey();
   }
 
   function netmonitorShouldBeSelected(aToolbox, panel)
   {
-      is (aToolbox.currentToolId, "netmonitor");
+      is (aToolbox.currentToolId, "netmonitor", "netmonitor should be selected.");
       finishUp();
   }
 
   function finishUp() {
     doc = node = inspector = keysetMap = null;
     gBrowser.removeCurrentTab();
     finish();
   }
--- a/browser/devtools/framework/test/browser_new_activation_workflow.js
+++ b/browser/devtools/framework/test/browser_new_activation_workflow.js
@@ -9,38 +9,38 @@ let toolbox, target;
 
 let tempScope = {};
 
 function test() {
   addTab("about:blank", function(aBrowser, aTab) {
     target = TargetFactory.forTab(gBrowser.selectedTab);
     loadWebConsole(aTab).then(function() {
       console.log('loaded');
-    }, console.error);
+    });
   });
 }
 
 function loadWebConsole(aTab) {
   ok(gDevTools, "gDevTools exists");
 
   return gDevTools.showToolbox(target, "webconsole").then(function(aToolbox) {
     toolbox = aToolbox;
     checkToolLoading();
-  }, console.error);
+  });
 }
 
 function checkToolLoading() {
   is(toolbox.currentToolId, "webconsole", "The web console is selected");
   ok(toolbox.isReady, "toolbox is ready")
 
   selectAndCheckById("jsdebugger").then(function() {
     selectAndCheckById("styleeditor").then(function() {
       testToggle();
     });
-  }, console.error);
+  });
 }
 
 function selectAndCheckById(id) {
   let doc = toolbox.frame.contentDocument;
 
   return toolbox.selectTool(id).then(function() {
     let tab = doc.getElementById("toolbox-tab-" + id);
     is(tab.hasAttribute("selected"), true, "The " + id + " tab is selected");
@@ -48,18 +48,20 @@ function selectAndCheckById(id) {
 }
 
 function testToggle() {
   toolbox.once("destroyed", function() {
     // Cannot reuse a target after it's destroyed.
     target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.showToolbox(target, "styleeditor").then(function(aToolbox) {
       toolbox = aToolbox;
-      is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
-      finishUp();
+      aToolbox.once("styleeditor-selected", () => {
+        is(toolbox.currentToolId, "styleeditor", "The style editor is selected");
+        finishUp();
+      });
     });
   }.bind(this));
 
   toolbox.destroy();
 }
 
 function finishUp() {
   toolbox.destroy().then(function() {
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -16,17 +16,21 @@ support-files =
 [browser_graphs-02.js]
 [browser_graphs-03.js]
 [browser_graphs-04.js]
 [browser_graphs-05.js]
 [browser_graphs-06.js]
 [browser_graphs-07.js]
 [browser_graphs-08.js]
 [browser_graphs-09.js]
-[browser_graphs-10.js]
+[browser_graphs-10a.js]
+[browser_graphs-10b.js]
+[browser_graphs-11.js]
+[browser_graphs-12.js]
+[browser_graphs-13.js]
 [browser_layoutHelpers.js]
 [browser_layoutHelpers-getBoxQuads.js]
 [browser_observableobject.js]
 [browser_outputparser.js]
 [browser_require_basic.js]
 [browser_telemetry_button_paintflashing.js]
 [browser_telemetry_button_responsive.js]
 [browser_telemetry_button_scratchpad.js]
--- a/browser/devtools/shared/test/browser_graphs-02.js
+++ b/browser/devtools/shared/test/browser_graphs-02.js
@@ -47,17 +47,17 @@ function testGraph(graph) {
     thrown2 = true;
   }
   ok(thrown2, "Setting regions twice shouldn't work.");
 
   ok(graph.hasData(), "The graph should now have the data source set.");
   ok(graph.hasRegions(), "The graph should now have the regions set.");
 
   is(graph.dataScaleX,
-     graph.width / 4180, // last key in TEST_DATA
+     graph.width / (4180 - 112), // last & first tick in TEST_DATA
     "The data scale on the X axis is correct.");
 
   is(graph.dataScaleY,
      graph.height / 60 * 0.85, // max value in TEST_DATA * GRAPH_DAMPEN_VALUES
     "The data scale on the Y axis is correct.");
 
   for (let i = 0; i < TEST_REGIONS.length; i++) {
     let original = TEST_REGIONS[i];
rename from browser/devtools/shared/test/browser_graphs-10.js
rename to browser/devtools/shared/test/browser_graphs-10a.js
--- a/browser/devtools/shared/test/browser_graphs-10.js
+++ b/browser/devtools/shared/test/browser_graphs-10a.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Tests that line graphs properly handle resizing.
+// Tests that graphs properly handle resizing.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
 let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
 
 let test = Task.async(function*() {
copy from browser/devtools/shared/test/browser_graphs-10.js
copy to browser/devtools/shared/test/browser_graphs-10b.js
--- a/browser/devtools/shared/test/browser_graphs-10.js
+++ b/browser/devtools/shared/test/browser_graphs-10b.js
@@ -1,12 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Tests that line graphs properly handle resizing.
+// Tests that graphs aren't refreshed when the owner window resizes but
+// the graph dimensions stay the same.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
 let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
 
 let test = Task.async(function*() {
@@ -16,127 +17,35 @@ let test = Task.async(function*() {
   finish();
 });
 
 function* performTest() {
   let [host, win, doc] = yield createHost("window");
   doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
 
   let graph = new LineGraphWidget(doc.body, "fps");
+  graph.fixedWidth = 200;
+  graph.fixedHeight = 100;
   yield graph.once("ready");
 
   let refreshCount = 0;
+  let refreshCancelledCount = 0;
   graph.on("refresh", () => refreshCount++);
+  graph.on("refresh-cancelled", () => refreshCancelledCount++);
 
   yield testGraph(host, graph);
 
-  is(refreshCount, 2, "The graph should've been refreshed 2 times.");
+  is(refreshCount, 0, "The graph shouldn't have been refreshed at all.");
+  is(refreshCancelledCount, 2, "The graph should've had 2 refresh attempts.");
 
   graph.destroy();
   host.destroy();
 }
 
 function* testGraph(host, graph) {
   graph.setData(TEST_DATA);
-  let initialBounds = host.frame.getBoundingClientRect();
 
   host._window.resizeBy(-100, -100);
-  yield graph.once("refresh");
-  let newBounds = host.frame.getBoundingClientRect();
-
-  is(initialBounds.width - newBounds.width, 100,
-    "The window was properly resized (1).");
-  is(initialBounds.height - newBounds.height, 100,
-    "The window was properly resized (2).");
-
-  is(graph.width, newBounds.width * window.devicePixelRatio,
-    "The graph has the correct width (1).");
-  is(graph.height, newBounds.height * window.devicePixelRatio,
-    "The graph has the correct height (1).");
-
-  info("Making a selection.");
-
-  dragStart(graph, 300);
-  ok(graph.hasSelectionInProgress(),
-    "The selection should start (1).");
-  is(graph.getSelection().start, 300,
-    "The current selection start value is correct (1).");
-  is(graph.getSelection().end, 300,
-    "The current selection end value is correct (1).");
-
-  hover(graph, 400);
-  ok(graph.hasSelectionInProgress(),
-    "The selection should still be in progress (2).");
-  is(graph.getSelection().start, 300,
-    "The current selection start value is correct (2).");
-  is(graph.getSelection().end, 400,
-    "The current selection end value is correct (2).");
-
-  dragStop(graph, 500);
-  ok(!graph.hasSelectionInProgress(),
-    "The selection should have stopped (3).");
-  is(graph.getSelection().start, 300,
-    "The current selection start value is correct (3).");
-  is(graph.getSelection().end, 500,
-    "The current selection end value is correct (3).");
+  yield graph.once("refresh-cancelled");
 
   host._window.resizeBy(100, 100);
-  yield graph.once("refresh");
-  let newerBounds = host.frame.getBoundingClientRect();
-
-  is(initialBounds.width - newerBounds.width, 0,
-    "The window was properly resized (3).");
-  is(initialBounds.height - newerBounds.height, 0,
-    "The window was properly resized (4).");
-
-  is(graph.width, newerBounds.width * window.devicePixelRatio,
-    "The graph has the correct width (2).");
-  is(graph.height, newerBounds.height * window.devicePixelRatio,
-    "The graph has the correct height (2).");
-
-  info("Making a new selection.");
-
-  dragStart(graph, 200);
-  ok(graph.hasSelectionInProgress(),
-    "The selection should start (4).");
-  is(graph.getSelection().start, 200,
-    "The current selection start value is correct (4).");
-  is(graph.getSelection().end, 200,
-    "The current selection end value is correct (4).");
-
-  hover(graph, 300);
-  ok(graph.hasSelectionInProgress(),
-    "The selection should still be in progress (5).");
-  is(graph.getSelection().start, 200,
-    "The current selection start value is correct (5).");
-  is(graph.getSelection().end, 300,
-    "The current selection end value is correct (5).");
-
-  dragStop(graph, 400);
-  ok(!graph.hasSelectionInProgress(),
-    "The selection should have stopped (6).");
-  is(graph.getSelection().start, 200,
-    "The current selection start value is correct (6).");
-  is(graph.getSelection().end, 400,
-    "The current selection end value is correct (6).");
+  yield graph.once("refresh-cancelled");
 }
-
-// EventUtils just doesn't work!
-
-function hover(graph, x, y = 1) {
-  x /= window.devicePixelRatio;
-  y /= window.devicePixelRatio;
-  graph._onMouseMove({ clientX: x, clientY: y });
-}
-
-function dragStart(graph, x, y = 1) {
-  x /= window.devicePixelRatio;
-  y /= window.devicePixelRatio;
-  graph._onMouseMove({ clientX: x, clientY: y });
-  graph._onMouseDown({ clientX: x, clientY: y });
-}
-
-function dragStop(graph, x, y = 1) {
-  x /= window.devicePixelRatio;
-  y /= window.devicePixelRatio;
-  graph._onMouseMove({ clientX: x, clientY: y });
-  graph._onMouseUp({ clientX: x, clientY: y });
-}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-11.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that bar graph create a legend as expected.
+
+let {BarGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
+
+const CATEGORIES = [
+  { color: "#46afe3", label: "Foo" },
+  { color: "#eb5368", label: "Bar" },
+  { color: "#70bf53", label: "Baz" }
+];
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  let graph = new BarGraphWidget(doc.body);
+  yield graph.once("ready");
+
+  testGraph(graph);
+
+  graph.destroy();
+  host.destroy();
+}
+
+function testGraph(graph) {
+  graph.format = CATEGORIES;
+  graph.setData([{ delta: 0, values: [] }]);
+
+  let legendContainer = graph._document.querySelector(".bar-graph-widget-legend");
+  ok(legendContainer,
+    "A legend container should be available.");
+  is(legendContainer.childNodes.length, 3,
+    "Three legend items should have been created.");
+
+  let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
+  is(legendItems.length, 3,
+    "Three legend items should exist in the entire graph.");
+
+  is(legendItems[0].querySelector("[view=color]").style.backgroundColor, "rgb(70, 175, 227)",
+    "The first legend item has the correct color.");
+  is(legendItems[1].querySelector("[view=color]").style.backgroundColor, "rgb(235, 83, 104)",
+    "The second legend item has the correct color.");
+  is(legendItems[2].querySelector("[view=color]").style.backgroundColor, "rgb(112, 191, 83)",
+    "The third legend item has the correct color.");
+
+  is(legendItems[0].querySelector("[view=label]").textContent, "Foo",
+    "The first legend item has the correct label.");
+  is(legendItems[1].querySelector("[view=label]").textContent, "Bar",
+    "The second legend item has the correct label.");
+  is(legendItems[2].querySelector("[view=label]").textContent, "Baz",
+    "The third legend item has the correct label.");
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-12.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that canvas graphs can have their selection linked.
+
+let {LineGraphWidget, BarGraphWidget, CanvasGraphUtils} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
+
+  let first = document.createElement("div");
+  first.setAttribute("style", "display: inline-block; width: 100%; height: 50%;");
+  doc.body.appendChild(first);
+
+  let second = document.createElement("div");
+  second.setAttribute("style", "display: inline-block; width: 100%; height: 50%;");
+  doc.body.appendChild(second);
+
+  let graph1 = new LineGraphWidget(first, "js");
+  let graph2 = new BarGraphWidget(second);
+
+  CanvasGraphUtils.linkAnimation(graph1, graph2);
+  CanvasGraphUtils.linkSelection(graph1, graph2);
+
+  yield graph1.ready();
+  yield graph2.ready();
+
+  testGraphs(graph1, graph2);
+
+  graph1.destroy();
+  graph2.destroy();
+  host.destroy();
+}
+
+function testGraphs(graph1, graph2) {
+  info("Making a selection in the first graph.");
+
+  dragStart(graph1, 300);
+  ok(graph1.hasSelectionInProgress(),
+    "The selection should start (1.1).");
+  ok(!graph2.hasSelectionInProgress(),
+    "The selection should not start yet in the second graph (1.2).");
+  is(graph1.getSelection().start, 300,
+    "The current selection start value is correct (1.1).");
+  is(graph2.getSelection().start, 300,
+    "The current selection start value is correct (1.2).");
+  is(graph1.getSelection().end, 300,
+    "The current selection end value is correct (1.1).");
+  is(graph2.getSelection().end, 300,
+    "The current selection end value is correct (1.2).");
+
+  hover(graph1, 400);
+  ok(graph1.hasSelectionInProgress(),
+    "The selection should still be in progress (2.1).");
+  ok(!graph2.hasSelectionInProgress(),
+    "The selection should not be in progress in the second graph (2.2).");
+  is(graph1.getSelection().start, 300,
+    "The current selection start value is correct (2.1).");
+  is(graph2.getSelection().start, 300,
+    "The current selection start value is correct (2.2).");
+  is(graph1.getSelection().end, 400,
+    "The current selection end value is correct (2.1).");
+  is(graph2.getSelection().end, 400,
+    "The current selection end value is correct (2.2).");
+
+  dragStop(graph1, 500);
+  ok(!graph1.hasSelectionInProgress(),
+    "The selection should have stopped (3.1).");
+  ok(!graph2.hasSelectionInProgress(),
+    "The selection should have stopped (3.2).");
+  is(graph1.getSelection().start, 300,
+    "The current selection start value is correct (3.1).");
+  is(graph2.getSelection().start, 300,
+    "The current selection start value is correct (3.2).");
+  is(graph1.getSelection().end, 500,
+    "The current selection end value is correct (3.1).");
+  is(graph2.getSelection().end, 500,
+    "The current selection end value is correct (3.2).");
+
+  info("Making a new selection in the second graph.");
+
+  dragStart(graph2, 200);
+  ok(!graph1.hasSelectionInProgress(),
+    "The selection should not start yet in the first graph (4.1).");
+  ok(graph2.hasSelectionInProgress(),
+    "The selection should start (4.2).");
+  is(graph1.getSelection().start, 200,
+    "The current selection start value is correct (4.1).");
+  is(graph2.getSelection().start, 200,
+    "The current selection start value is correct (4.2).");
+  is(graph1.getSelection().end, 200,
+    "The current selection end value is correct (4.1).");
+  is(graph2.getSelection().end, 200,
+    "The current selection end value is correct (4.2).");
+
+  hover(graph2, 300);
+  ok(!graph1.hasSelectionInProgress(),
+    "The selection should not be in progress in the first graph (2.2).");
+  ok(graph2.hasSelectionInProgress(),
+    "The selection should still be in progress (5.2).");
+  is(graph1.getSelection().start, 200,
+    "The current selection start value is correct (5.1).");
+  is(graph2.getSelection().start, 200,
+    "The current selection start value is correct (5.2).");
+  is(graph1.getSelection().end, 300,
+    "The current selection end value is correct (5.1).");
+  is(graph2.getSelection().end, 300,
+    "The current selection end value is correct (5.2).");
+
+  dragStop(graph2, 400);
+  ok(!graph1.hasSelectionInProgress(),
+    "The selection should have stopped (6.1).");
+  ok(!graph2.hasSelectionInProgress(),
+    "The selection should have stopped (6.2).");
+  is(graph1.getSelection().start, 200,
+    "The current selection start value is correct (6.1).");
+  is(graph2.getSelection().start, 200,
+    "The current selection start value is correct (6.2).");
+  is(graph1.getSelection().end, 400,
+    "The current selection end value is correct (6.1).");
+  is(graph2.getSelection().end, 400,
+    "The current selection end value is correct (6.2).");
+}
+
+// EventUtils just doesn't work!
+
+function hover(graph, x, y = 1) {
+  x /= window.devicePixelRatio;
+  y /= window.devicePixelRatio;
+  graph._onMouseMove({ clientX: x, clientY: y });
+}
+
+function dragStart(graph, x, y = 1) {
+  x /= window.devicePixelRatio;
+  y /= window.devicePixelRatio;
+  graph._onMouseMove({ clientX: x, clientY: y });
+  graph._onMouseDown({ clientX: x, clientY: y });
+}
+
+function dragStop(graph, x, y = 1) {
+  x /= window.devicePixelRatio;
+  y /= window.devicePixelRatio;
+  graph._onMouseMove({ clientX: x, clientY: y });
+  graph._onMouseUp({ clientX: x, clientY: y });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-13.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that graph widgets may have a fixed width or height.
+
+let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
+
+  let graph = new LineGraphWidget(doc.body, "fps");
+  graph.fixedWidth = 200;
+  graph.fixedHeight = 100;
+
+  yield graph.ready();
+  testGraph(host, graph);
+
+  graph.destroy();
+  host.destroy();
+}
+
+function testGraph(host, graph) {
+  let bounds = host.frame.getBoundingClientRect();
+
+  isnot(graph.width, bounds.width * window.devicePixelRatio,
+    "The graph should not span all the parent node's width.");
+  isnot(graph.height, bounds.height * window.devicePixelRatio,
+    "The graph should not span all the parent node's height.");
+
+  is(graph.width, graph.fixedWidth * window.devicePixelRatio,
+    "The graph has the correct width.");
+  is(graph.height, graph.fixedHeight * window.devicePixelRatio,
+    "The graph has the correct height.");
+}
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -3,26 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 
-this.EXPORTED_SYMBOLS = ["LineGraphWidget"];
+this.EXPORTED_SYMBOLS = ["LineGraphWidget", "BarGraphWidget", "CanvasGraphUtils"];
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
 
 // Generic constants.
 
-const GRAPH_DAMPEN_VALUES = 0.85;
-const GRAPH_RESIZE_EVENTS_DRAIN = 20; // ms
+const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
 const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00075;
 const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.1;
 const GRAPH_WHEEL_MIN_SELECTION_WIDTH = 10; // px
 
 const GRAPH_SELECTION_BOUNDARY_HOVER_LINE_WIDTH = 4; // px
 const GRAPH_SELECTION_BOUNDARY_HOVER_THRESHOLD = 10; // px
 const GRAPH_MAX_SELECTION_LEFT_PADDING = 1;
 const GRAPH_MAX_SELECTION_RIGHT_PADDING = 1;
@@ -32,17 +32,20 @@ const GRAPH_REGION_LINE_COLOR = "rgba(23
 
 const GRAPH_STRIPE_PATTERN_WIDTH = 16; // px
 const GRAPH_STRIPE_PATTERN_HEIGHT = 16; // px
 const GRAPH_STRIPE_PATTERN_LINE_WIDTH = 4; // px
 const GRAPH_STRIPE_PATTERN_LINE_SPACING = 8; // px
 
 // Line graph constants.
 
+const LINE_GRAPH_DAMPEN_VALUES = 0.85;
 const LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS = 400; // 20 px
+const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 10; // px
+
 const LINE_GRAPH_STROKE_WIDTH = 2; // px
 const LINE_GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
 const LINE_GRAPH_HELPER_LINES_DASH = [5]; // px
 const LINE_GRAPH_HELPER_LINES_WIDTH = 1; // px
 const LINE_GRAPH_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
 const LINE_GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
 const LINE_GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
 const LINE_GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
@@ -50,17 +53,33 @@ const LINE_GRAPH_BACKGROUND_GRADIENT_END
 
 const LINE_GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
 const LINE_GRAPH_SELECTION_LINE_COLOR = "#fff";
 const LINE_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
 const LINE_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
 const LINE_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
 const LINE_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
 
-const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 10; // px
+// Bar graph constants.
+
+const BAR_GRAPH_DAMPEN_VALUES = 0.75;
+const BAR_GRAPH_BARS_MARGIN_TOP = 1; // px
+const BAR_GRAPH_BARS_MARGIN_END = 2; // px
+const BAR_GRAPH_MIN_BARS_WIDTH = 5; // px
+const BAR_GRAPH_MIN_BLOCKS_HEIGHT = 1; // px
+
+const BAR_GRAPH_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.0)";
+const BAR_GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.25)";
+
+const BAR_GRAPH_CLIPHEAD_LINE_COLOR = "#666";
+const BAR_GRAPH_SELECTION_LINE_COLOR = "#555";
+const BAR_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(0,136,204,0.25)";
+const BAR_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
+const BAR_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
+const BAR_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
 
 /**
  * Small data primitives for all graphs.
  */
 this.GraphCursor = function() {}
 this.GraphSelection = function() {}
 this.GraphSelectionDragger = function() {}
 this.GraphSelectionResizer = function() {}
@@ -123,22 +142,24 @@ this.AbstractCanvasGraph = function(pare
 
   AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
     this._iframe = iframe;
     this._window = iframe.contentWindow;
     this._document = iframe.contentDocument;
     this._pixelRatio = sharpness || this._window.devicePixelRatio;
 
     let container = this._container = this._document.getElementById("graph-container");
-    container.className = name + "-widget-container";
+    container.className = name + "-widget-container graph-widget-container";
 
     let canvas = this._canvas = this._document.getElementById("graph-canvas");
     canvas.className = name + "-widget-canvas graph-widget-canvas";
 
     let bounds = parent.getBoundingClientRect();
+    bounds.width = this.fixedWidth || bounds.width;
+    bounds.height = this.fixedHeight || bounds.height;
     iframe.setAttribute("width", bounds.width);
     iframe.setAttribute("height", bounds.height);
 
     this._width = canvas.width = bounds.width * this._pixelRatio;
     this._height = canvas.height = bounds.height * this._pixelRatio;
     this._ctx = canvas.getContext("2d");
     this._ctx.mozImageSmoothingEnabled = false;
 
@@ -222,16 +243,23 @@ AbstractCanvasGraph.prototype = {
   selectionLineWidth: 1,
   selectionLineColor: "transparent",
   selectionBackgroundColor: "transparent",
   selectionStripesColor: "transparent",
   regionBackgroundColor: "transparent",
   regionStripesColor: "transparent",
 
   /**
+   * Makes sure the canvas graph is of the specified width or height, and
+   * doesn't flex to fit all the available space.
+   */
+  fixedWidth: null,
+  fixedHeight: null,
+
+  /**
    * Builds and caches a graph image, based on the data source supplied
    * in `setData`. The graph image is not rebuilt on each frame, but
    * only when the data source changes.
    */
   buildGraphImage: function() {
     throw "This method needs to be implemented by inheriting classes.";
   },
 
@@ -335,16 +363,19 @@ AbstractCanvasGraph.prototype = {
     }
     return { start: null, end: null };
   },
 
   /**
    * Removes the selection.
    */
   dropSelection: function() {
+    if (!this.hasSelection() && !this.hasSelectionInProgress()) {
+      return;
+    }
     this._selection.start = null;
     this._selection.end = null;
     this._shouldRedraw = true;
     this.emit("deselecting");
   },
 
   /**
    * Gets whether or not this graph has a selection.
@@ -392,16 +423,19 @@ AbstractCanvasGraph.prototype = {
   getCursor: function() {
     return { x: this._cursor.x, y: this._cursor.y };
   },
 
   /**
    * Hides the cursor.
    */
   dropCursor: function() {
+    if (!this.hasCursor()) {
+      return;
+    }
     this._cursor.x = null;
     this._cursor.y = null;
     this._shouldRedraw = true;
   },
 
   /**
    * Gets whether or not this graph has a visible cursor.
    * @return boolean
@@ -463,16 +497,28 @@ AbstractCanvasGraph.prototype = {
       (start > end && end < x && start > x));
   },
 
   /**
    * Updates this graph to reflect the new dimensions of the parent node.
    */
   refresh: function() {
     let bounds = this._parent.getBoundingClientRect();
+    let newWidth = this.fixedWidth || bounds.width;
+    let newHeight = this.fixedHeight || bounds.height;
+
+    // Prevent redrawing everything if the graph's width & height won't change.
+    if (this._width == newWidth * this._pixelRatio &&
+        this._height == newHeight * this._pixelRatio) {
+      this.emit("refresh-cancelled");
+      return;
+    }
+
+    bounds.width = newWidth;
+    bounds.height = newHeight;
     this._iframe.setAttribute("width", bounds.width);
     this._iframe.setAttribute("height", bounds.height);
     this._width = this._canvas.width = bounds.width * this._pixelRatio;
     this._height = this._canvas.height = bounds.height * this._pixelRatio;
 
     if (this._data) {
       this._cachedGraphImage = this.buildGraphImage();
     }
@@ -920,16 +966,33 @@ AbstractCanvasGraph.prototype = {
     }
   }
 };
 
 /**
  * A basic line graph, plotting values on a curve and adding helper lines
  * and tooltips for maximum, average and minimum values.
  *
+ * @see AbstractCanvasGraph for emitted events and other options.
+ *
+ * Example usage:
+ *   let graph = new LineGraphWidget(node, "units");
+ *   graph.once("ready", () => {
+ *     graph.setData(src);
+ *   });
+ *
+ * Data source format:
+ *   [
+ *     { delta: x1, value: y1 },
+ *     { delta: x2, value: y2 },
+ *     ...
+ *     { delta: xn, value: yn }
+ *   ]
+ * where each item in the array represents a point in the graph.
+ *
  * @param nsIDOMNode parent
  *        The parent node holding the graph.
  * @param string metric [optional]
  *        The metric displayed in the graph, e.g. "fps" or "bananas".
  */
 this.LineGraphWidget = function(parent, metric, ...args) {
   AbstractCanvasGraph.apply(this, [parent, "line-graph", ...args]);
 
@@ -976,18 +1039,18 @@ LineGraphWidget.prototype = Heritage.ext
     let sumValues = 0;
 
     for (let { delta, value } of this._data) {
       maxValue = Math.max(value, maxValue);
       minValue = Math.min(value, minValue);
       sumValues += value;
     }
 
-    let dataScaleX = this.dataScaleX = width / lastTick;
-    let dataScaleY = this.dataScaleY = height / maxValue * GRAPH_DAMPEN_VALUES;
+    let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
+    let dataScaleY = this.dataScaleY = height / maxValue * LINE_GRAPH_DAMPEN_VALUES;
 
     /**
      * Calculates the squared distance between two 2D points.
      */
     function distSquared(x0, y0, x1, y1) {
       let xs = x1 - x0;
       let ys = y1 - y0;
       return xs * xs + ys * ys;
@@ -1002,26 +1065,26 @@ LineGraphWidget.prototype = Heritage.ext
     ctx.strokeStyle = LINE_GRAPH_STROKE_COLOR;
     ctx.lineWidth = LINE_GRAPH_STROKE_WIDTH;
     ctx.beginPath();
 
     let prevX = 0;
     let prevY = 0;
 
     for (let { delta, value } of this._data) {
-      let currX = delta * dataScaleX;
+      let currX = (delta - firstTick) * dataScaleX;
       let currY = height - value * dataScaleY;
 
       if (delta == firstTick) {
         ctx.moveTo(-LINE_GRAPH_STROKE_WIDTH, height);
         ctx.lineTo(-LINE_GRAPH_STROKE_WIDTH, currY);
       }
 
       let distance = distSquared(prevX, prevY, currX, currY);
-      if (distance > this.minDistanceBetweenPoints) {
+      if (distance >= this.minDistanceBetweenPoints) {
         ctx.lineTo(currX, currY);
         prevX = currX;
         prevY = currY;
       }
 
       if (delta == lastTick) {
         ctx.lineTo(width + LINE_GRAPH_STROKE_WIDTH, currY);
         ctx.lineTo(width + LINE_GRAPH_STROKE_WIDTH, height);
@@ -1083,19 +1146,19 @@ LineGraphWidget.prototype = Heritage.ext
      */
     function clamp(value, min, max) {
       if (value < min) return min;
       if (value > max) return max;
       return value;
     }
 
     let bottom = height / this._pixelRatio;
-    let maxPosY = map(maxValue * GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
-    let avgPosY = map(avgValue * GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
-    let minPosY = map(minValue * GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
+    let maxPosY = map(maxValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
+    let avgPosY = map(avgValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
+    let minPosY = map(minValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
 
     let safeTop = LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
     let safeBottom = bottom - LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
 
     this._maxTooltip.style.top = clamp(maxPosY, safeTop, safeBottom) + "px";
     this._avgTooltip.style.top = clamp(avgPosY, safeTop, safeBottom) + "px";
     this._minTooltip.style.top = clamp(minPosY, safeTop, safeBottom) + "px";
     this._maxGutterLine.style.top = clamp(maxPosY, safeTop, safeBottom) + "px";
@@ -1156,16 +1219,255 @@ LineGraphWidget.prototype = Heritage.ext
     tooltip.appendChild(valueNode);
     tooltip.appendChild(metricNode);
     this._container.appendChild(tooltip);
 
     return tooltip;
   }
 });
 
+
+/**
+ * A bar graph, plotting tuples of values as rectangles.
+ *
+ * @see AbstractCanvasGraph for emitted events and other options.
+ *
+ * Example usage:
+ *   let graph = new BarGraphWidget(node);
+ *   graph.format = ...;
+ *   graph.once("ready", () => {
+ *     graph.setData(src);
+ *   });
+ *
+ * The `graph.format` traits are mandatory and will determine how the values
+ * are styled as "blocks" in every "bar":
+ *   [
+ *     { color: "#f00", label: "Foo" },
+ *     { color: "#0f0", label: "Bar" },
+ *     ...
+ *     { color: "#00f", label: "Baz" }
+ *   ]
+ *
+ * Data source format:
+ *   [
+ *     { delta: x1, values: [y11, y12, ... y1n] },
+ *     { delta: x2, values: [y21, y22, ... y2n] },
+ *     ...
+ *     { delta: xm, values: [ym1, ym2, ... ymn] }
+ *   ]
+ * where each item in the array represents a "bar", for which every value
+ * represents a "block" inside that "bar", plotted at the "delta" position.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the graph.
+ */
+this.BarGraphWidget = function(parent, ...args) {
+  AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
+
+  this.once("ready", () => {
+    this._createLegend();
+  });
+}
+
+BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
+  clipheadLineColor: BAR_GRAPH_CLIPHEAD_LINE_COLOR,
+  selectionLineColor: BAR_GRAPH_SELECTION_LINE_COLOR,
+  selectionBackgroundColor: BAR_GRAPH_SELECTION_BACKGROUND_COLOR,
+  selectionStripesColor: BAR_GRAPH_SELECTION_STRIPES_COLOR,
+  regionBackgroundColor: BAR_GRAPH_REGION_BACKGROUND_COLOR,
+  regionStripesColor: BAR_GRAPH_REGION_STRIPES_COLOR,
+
+  /**
+   * List of colors used to fill each block inside every bar, also
+   * corresponding to labels displayed in this graph's legend.
+   */
+  format: null,
+
+  /**
+   * Bars that are too close too each other in the graph will be combined.
+   * This scalar specifies the required minimum width of each bar.
+   */
+  minBarsWidth: BAR_GRAPH_MIN_BARS_WIDTH,
+
+  /**
+   * Blocks in a bar that are too thin inside the bar will not be rendered.
+   * This scalar specifies the required minimum height of each block.
+   */
+  minBlocksHeight: BAR_GRAPH_MIN_BLOCKS_HEIGHT,
+
+  /**
+   * Renders the graph on a canvas.
+   * @see AbstractCanvasGraph.prototype.buildGraphImage
+   */
+  buildGraphImage: function() {
+    if (!this.format || !this.format.length) {
+      throw "The graph format traits are mandatory to style the data source.";
+    }
+
+    let canvas = this._document.createElementNS(HTML_NS, "canvas");
+    let ctx = canvas.getContext("2d");
+    let width = canvas.width = this._width;
+    let height = canvas.height = this._height;
+
+    let totalTypes = this.format.length;
+    let totalTicks = this._data.length;
+    let firstTick = this._data[0].delta;
+    let lastTick = this._data[totalTicks - 1].delta;
+
+    let minBarsWidth = this.minBarsWidth * this._pixelRatio;
+    let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
+
+    let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
+    let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
+      data: this._data,
+      dataScaleX: dataScaleX,
+      dataOffsetX: firstTick,
+      minBarsWidth: minBarsWidth
+    }) * BAR_GRAPH_DAMPEN_VALUES;
+
+    // Draw the background.
+
+    let gradient = ctx.createLinearGradient(0, 0, 0, height);
+    gradient.addColorStop(0, BAR_GRAPH_BACKGROUND_GRADIENT_START);
+    gradient.addColorStop(1, BAR_GRAPH_BACKGROUND_GRADIENT_END);
+    ctx.fillStyle = gradient;
+    ctx.fillRect(0, 0, width, height);
+
+    // Draw the graph.
+
+    // Iterate over the blocks, then the bars, to draw all rectangles of
+    // the same color in a single pass. See the @constructor for more
+    // information about the data source, and how a "bar" contains "blocks".
+
+    let prevHeight = [];
+
+    for (let type = 0; type < totalTypes; type++) {
+      ctx.fillStyle = this.format[type].color || "#000";
+      ctx.beginPath();
+
+      let prevLeft = 0;
+      let skippedCount = 0;
+      let skippedHeight = 0;
+
+      for (let tick = 0; tick < totalTicks; tick++) {
+        let delta = this._data[tick].delta;
+        let value = this._data[tick].values[type] || 0;
+        let blockLeft = (delta - firstTick) * dataScaleX;
+        let blockHeight = value * dataScaleY;
+
+        let blockWidth = blockLeft - prevLeft;
+        if (blockWidth < minBarsWidth) {
+          skippedCount++;
+          skippedHeight += blockHeight;
+          continue;
+        }
+
+        let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
+        if (averageHeight >= minBlocksHeight) {
+          let bottom = height - ~~prevHeight[tick];
+          ctx.moveTo(prevLeft, bottom);
+          ctx.lineTo(prevLeft, bottom - averageHeight);
+          ctx.lineTo(blockLeft, bottom - averageHeight);
+          ctx.lineTo(blockLeft, bottom);
+
+          if (prevHeight[tick] === undefined) {
+            prevHeight[tick] = averageHeight + BAR_GRAPH_BARS_MARGIN_TOP;
+          } else {
+            prevHeight[tick] += averageHeight + BAR_GRAPH_BARS_MARGIN_TOP;
+          }
+        }
+
+        prevLeft += blockWidth + BAR_GRAPH_BARS_MARGIN_END;
+        skippedHeight = 0;
+        skippedCount = 0;
+      }
+
+      ctx.fill();
+    }
+
+    // Update the legend.
+
+    while (this._legendNode.hasChildNodes()) {
+      this._legendNode.firstChild.remove();
+    }
+    for (let { color, label } of this.format) {
+      this._createLegendItem(color, label);
+    }
+
+    return canvas;
+  },
+
+  /**
+   * Calculates the height of the tallest bar that would eventially be rendered
+   * in this graph.
+   *
+   * Bars that are too close too each other in the graph will be combined.
+   * @see `minBarsWidth`
+   *
+   * @return number
+   *         The tallest bar height in this graph.
+   */
+  _calcMaxHeight: function({ data, dataScaleX, dataOffsetX, minBarsWidth }) {
+    let maxHeight = 0;
+    let prevLeft = 0;
+    let skippedCount = 0;
+    let skippedHeight = 0;
+
+    for (let { delta, values } of data) {
+      let barLeft = (delta - dataOffsetX) * dataScaleX;
+      let barHeight = values.reduce((a, b) => a + b, 0);
+
+      let barWidth = barLeft - prevLeft;
+      if (barWidth < minBarsWidth) {
+        skippedCount++;
+        skippedHeight += barHeight;
+        continue;
+      }
+
+      let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
+      maxHeight = Math.max(averageHeight, maxHeight);
+
+      prevLeft += barWidth;
+      skippedHeight = 0;
+      skippedCount = 0;
+    }
+
+    return maxHeight;
+  },
+
+  /**
+   * Creates the legend container when constructing this graph.
+   */
+  _createLegend: function() {
+    let legendNode = this._legendNode = this._document.createElementNS(HTML_NS, "div");
+    legendNode.className = "bar-graph-widget-legend";
+    this._container.appendChild(legendNode);
+  },
+
+  /**
+   * Creates a legend item when constructing this graph.
+   */
+  _createLegendItem: function(color, label) {
+    let itemNode = this._document.createElementNS(HTML_NS, "div");
+    itemNode.className = "bar-graph-widget-legend-item";
+
+    let colorNode = this._document.createElementNS(HTML_NS, "span");
+    colorNode.setAttribute("view", "color");
+    colorNode.style.backgroundColor = color;
+
+    let labelNode = this._document.createElementNS(HTML_NS, "span");
+    labelNode.setAttribute("view", "label");
+    labelNode.textContent = label;
+
+    itemNode.appendChild(colorNode);
+    itemNode.appendChild(labelNode);
+    this._legendNode.appendChild(itemNode);
+  }
+});
+
 // Helper functions.
 
 /**
  * Creates an iframe element with the provided source URL, appends it to
  * the specified node and invokes the callback once the content is loaded.
  *
  * @param string url
  *        The desired source URL for the iframe.
@@ -1231,8 +1533,51 @@ AbstractCanvasGraph.getStripePattern = f
   gCachedStripePattern.set(id, pattern);
   return pattern;
 };
 
 /**
  * Cache used by `AbstractCanvasGraph.getStripePattern`.
  */
 const gCachedStripePattern = new Map();
+
+/**
+ * Utility functions for graph canvases.
+ */
+this.CanvasGraphUtils = {
+  /**
+   * Merges the animation loop of two graphs.
+   */
+  linkAnimation: Task.async(function*(graph1, graph2) {
+    yield graph1.ready();
+    yield graph2.ready();
+
+    let window = graph1._window;
+    window.cancelAnimationFrame(graph1._animationId);
+    window.cancelAnimationFrame(graph2._animationId);
+
+    let loop = () => {
+      window.requestAnimationFrame(loop);
+      graph1._drawWidget();
+      graph2._drawWidget();
+    };
+
+    window.requestAnimationFrame(loop);
+  }),
+
+  /**
+   * Makes sure selections in one graph are reflected in another.
+   */
+  linkSelection: function(graph1, graph2) {
+    graph1.on("selecting", () => {
+      graph2.setSelection(graph1.getSelection());
+    });
+    graph2.on("selecting", () => {
+      graph1.setSelection(graph2.getSelection());
+    });
+    graph1.on("deselecting", () => {
+      graph2.dropSelection();
+    });
+    graph2.on("deselecting", () => {
+      graph1.dropSelection();
+    });
+  }
+};
--- a/browser/locales/filter.py
+++ b/browser/locales/filter.py
@@ -15,17 +15,20 @@ def test(mod, path, entity = None):
   if mod not in ("browser", "extensions/spellcheck"):
     # we only have exceptions for browser and extensions/spellcheck
     return "error"
   if not entity:
     # the only files to ignore are spell checkers and search
     if mod == "extensions/spellcheck":
       return "ignore"
     # browser
-    return "ignore" if re.match(r"searchplugins\/.+\.xml", path) else "error"
+    if (re.match(r"searchplugins\/.+\.xml", path) or
+        path == "searchplugins/metrolist.txt"):
+      return "ignore"
+    return "error"
   if mod == "extensions/spellcheck":
     # l10n ships en-US dictionary or something, do compare
     return "error"
   if path == "defines.inc":
     return "ignore" if entity == "MOZ_LANGPACK_CONTRIBUTORS" else "error"
 
   if mod == "browser" and path == "chrome/browser-region/region.properties":
     # only region.properties exceptions remain, compare all others
--- a/browser/themes/shared/devtools/widgets.inc.css
+++ b/browser/themes/shared/devtools/widgets.inc.css
@@ -884,16 +884,20 @@
 
 .variable-or-property[unmatched] {
   border: none;
   margin: 0;
 }
 
 /* Canvas graphs */
 
+.graph-widget-container {
+  position: relative;
+}
+
 .graph-widget-canvas {
   width: 100%;
   height: 100%;
 }
 
 .graph-widget-canvas[input=hovering-background] {
   cursor: text;
 }
@@ -917,20 +921,16 @@
 }
 
 .graph-widget-canvas ~ * {
   pointer-events: none;
 }
 
 /* Line graph widget */
 
-.line-graph-widget-container {
-  position: relative;
-}
-
 .line-graph-widget-canvas {
   background: #0088cc;
 }
 
 .line-graph-widget-gutter {
   position: absolute;
   background: rgba(255,255,255,0.75);
   width: 10px;
@@ -1030,16 +1030,56 @@
 .line-graph-widget-tooltip[type=minimum] > [text=value] {
   color: #ed2655;
 }
 
 .line-graph-widget-tooltip[type=average] > [text=value] {
   color: #d97e00;
 }
 
+/* Bar graph widget */
+
+.bar-graph-widget-canvas {
+  background: #f7f7f7;
+}
+
+.bar-graph-widget-legend {
+  position: absolute;
+  top: 4px;
+  left: 8px;
+  color: #292e33;
+  font-size: 80%;
+}
+
+.bar-graph-widget-legend-item {
+  float: left;
+  -moz-margin-end: 8px;
+}
+
+.bar-graph-widget-legend-item > [view="color"],
+.bar-graph-widget-legend-item > [view="label"] {
+  vertical-align: middle;
+}
+
+.bar-graph-widget-legend-item > [view="color"] {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  border: 1px solid #fff;
+  border-radius: 1px;
+  -moz-margin-end: 4px;
+}
+
+.bar-graph-widget-legend-item > [view="label"] {
+  text-shadow: 1px  0px rgba(255,255,255,0.8),
+              -1px  0px rgba(255,255,255,0.8),
+               0px -1px rgba(255,255,255,0.8),
+               0px  1px rgba(255,255,255,0.8);
+}
+
 /* Charts */
 
 .generic-chart-container {
   /* Hack: force hardware acceleration */
   transform: translateZ(1px);
 }
 
 .theme-dark .generic-chart-container {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2412,23 +2412,16 @@ notification[value="translation"] {
   notification[value="translation"] button[type="menu"] > .button-box > .button-menu-dropmarker {
     padding: 0;
     -moz-margin-start: 3ch;
   }
 
   notification[value="translation"] button:not([type="menu"]) > .button-box {
     -moz-margin-end: 3ch;
   }
-
-  @media (min-resolution: 1.25dppx) {
-    notification[value="translation"] button[type="menu"] > .button-box > .button-menu-dropmarker,
-    notification[value="translation"] menulist > .menulist-dropmarker {
-      list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow@2x.png");
-    }
-  }
 }
 
 .translate-notification-icon,
 #translate-notification-icon {
   list-style-image: url(chrome://browser/skin/translation-16.png);
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 
--- a/dom/bluetooth/tests/marionette/test_dom_BluetoothManager_enabled.js
+++ b/dom/bluetooth/tests/marionette/test_dom_BluetoothManager_enabled.js
@@ -28,29 +28,34 @@ function waitEitherEnabledOrDisabled() {
   return deferred.promise;
 }
 
 function test(aEnabled) {
   log("Testing 'bluetooth.enabled' => " + aEnabled);
 
   let deferred = Promise.defer();
 
-  Promise.all([setBluetoothEnabled(aEnabled),
-               waitEitherEnabledOrDisabled()])
+  // Ensures that we can always receive that "enabled"/"disabled" event by
+  // installing the event handler *before* we ever enable/disable Bluetooth. Or
+  // we might just miss those events and get a timeout error.
+  let promises = [];
+  promises.push(waitEitherEnabledOrDisabled());
+  promises.push(setBluetoothEnabled(aEnabled));
+  Promise.all(promises)
     .then(function(aResults) {
       /* aResults is an array of two elements:
-       *   [ <result of setBluetoothEnabled>,
-       *     <result of waitEitherEnabledOrDisabled> ]
+       *   [ <result of waitEitherEnabledOrDisabled>,
+       *     <result of setBluetoothEnabled>]
        */
       log("  Examine results " + JSON.stringify(aResults));
 
       is(bluetoothManager.enabled, aEnabled, "bluetoothManager.enabled");
-      is(aResults[1], aEnabled, "'adapteradded' event received");
+      is(aResults[0], aEnabled, "'adapteradded' event received");
 
-      if (bluetoothManager.enabled === aEnabled && aResults[1] === aEnabled) {
+      if (bluetoothManager.enabled === aEnabled && aResults[0] === aEnabled) {
         deferred.resolve();
       } else {
         deferred.reject();
       }
     });
 
   return deferred.promise;
 }
--- a/dom/bluetooth2/BluetoothManager.cpp
+++ b/dom/bluetooth2/BluetoothManager.cpp
@@ -255,17 +255,17 @@ BluetoothManager::DispatchAttributeEvent
     nsCOMPtr<nsIGlobalObject> global =
       do_QueryInterface(adapter->GetParentObject());
     NS_ENSURE_TRUE_VOID(global);
 
     JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
     NS_ENSURE_TRUE_VOID(scope);
 
     JSAutoCompartment ac(cx, scope);
-    if (!ToJSValue(cx, adapter, &value)) {
+    if (!ToJSValue(cx, mAdapters[mDefaultAdapterIndex], &value)) {
       JS_ClearPendingException(cx);
       return;
     }
 
     BT_API2_LOGR("Default adapter is wrapped");
   }
 
   // Notify application of default adapter change
--- a/dom/bluetooth2/BluetoothRilListener.cpp
+++ b/dom/bluetooth2/BluetoothRilListener.cpp
@@ -176,16 +176,19 @@ MobileConnectionListener::Listen(bool aS
  */
 NS_IMPL_ISUPPORTS(TelephonyListener, nsITelephonyListener)
 
 NS_IMETHODIMP
 TelephonyListener::CallStateChanged(uint32_t aServiceId,
                                     uint32_t aCallIndex,
                                     uint16_t aCallState,
                                     const nsAString& aNumber,
+                                    uint16_t aNumberPresentation,
+                                    const nsAString& aName,
+                                    uint16_t aNamePresentation,
                                     bool aIsOutgoing,
                                     bool aIsEmergency,
                                     bool aIsConference,
                                     bool aIsSwitchable,
                                     bool aIsMergeable)
 {
   BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
   NS_ENSURE_TRUE(hfp, NS_ERROR_FAILURE);
@@ -195,16 +198,19 @@ TelephonyListener::CallStateChanged(uint
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelephonyListener::EnumerateCallState(uint32_t aServiceId,
                                       uint32_t aCallIndex,
                                       uint16_t aCallState,
                                       const nsAString_internal& aNumber,
+                                      uint16_t aNumberPresentation,
+                                      const nsAString& aName,
+                                      uint16_t aNamePresentation,
                                       bool aIsOutgoing,
                                       bool aIsEmergency,
                                       bool aIsConference,
                                       bool aIsSwitchable,
                                       bool aIsMergeable)
 {
   BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
   NS_ENSURE_TRUE(hfp, NS_ERROR_FAILURE);
@@ -265,17 +271,20 @@ TelephonyListener::NotifyConferenceError
   BT_WARNING(NS_ConvertUTF16toUTF8(aName).get());
   BT_WARNING(NS_ConvertUTF16toUTF8(aMessage).get());
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelephonyListener::NotifyCdmaCallWaiting(uint32_t aServiceId,
-                                         const nsAString& aNumber)
+                                         const nsAString& aNumber,
+                                         uint16_t aNumberPresentation,
+                                         const nsAString& aName,
+                                         uint16_t aNamePresentation)
 {
   BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
   NS_ENSURE_TRUE(hfp, NS_ERROR_FAILURE);
 
   hfp->UpdateSecondNumber(aNumber);
 
   return NS_OK;
 }
--- a/dom/bluetooth2/BluetoothService.cpp
+++ b/dom/bluetooth2/BluetoothService.cpp
@@ -20,17 +20,16 @@
 #include "BluetoothUtils.h"
 
 #include "jsapi.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "mozilla/ipc/UnixSocket.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPCOM.h"
@@ -158,17 +157,16 @@ BluetoothService::ToggleBtAck::Run()
     return NS_OK;
   }
 
   // Update mEnabled of BluetoothService object since
   // StartInternal/StopInternal have been already done.
   sBluetoothService->SetEnabled(mEnabled);
   sToggleInProgress = false;
 
-  sBluetoothService->TryFiringAdapterAdded();
   sBluetoothService->FireAdapterStateChanged(mEnabled);
 
   return NS_OK;
 }
 
 class BluetoothService::StartupTask : public nsISettingsServiceCallback
 {
 public:
@@ -371,18 +369,16 @@ BluetoothService::StartBluetooth(bool aI
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sInShutdown) {
     // Don't try to start if we're already shutting down.
     MOZ_ASSERT(false, "Start called while in shutdown!");
     return NS_ERROR_FAILURE;
   }
 
-  mAdapterAddedReceived = false;
-
   /* When IsEnabled() is true, we don't switch on Bluetooth but we still
    * send ToggleBtAck task. One special case happens at startup stage. At
    * startup, the initialization of BluetoothService still has to be done
    * even if Bluetooth is already enabled.
    *
    * Please see bug 892392 for more information.
    */
   if (aIsStartup || !sBluetoothService->IsEnabled()) {
@@ -433,18 +429,16 @@ BluetoothService::StopBluetooth(bool aIs
   profile = BluetoothHidManager::Get();
   NS_ENSURE_TRUE(profile, NS_ERROR_FAILURE);
   if (profile->IsConnected()) {
     profile->Disconnect(nullptr);
   } else {
     profile->Reset();
   }
 
-  mAdapterAddedReceived = false;
-
   /* When IsEnabled() is false, we don't switch off Bluetooth but we still
    * send ToggleBtAck task. One special case happens at startup stage. At
    * startup, the initialization of BluetoothService still has to be done
    * even if Bluetooth is disabled.
    *
    * Please see bug 892392 for more information.
    */
   if (aIsStartup || sBluetoothService->IsEnabled()) {
@@ -489,18 +483,18 @@ BluetoothService::SetEnabled(bool aEnabl
     unused << childActors[index]->SendEnabled(aEnabled);
   }
 
   /**
    * mEnabled: real status of bluetooth
    * aEnabled: expected status of bluetooth
    */
   if (mEnabled == aEnabled) {
-    BT_WARNING("Bluetooth has already been enabled/disabled before "
-               "or the toggling is failed.");
+    BT_WARNING("Bluetooth is already %s, or the toggling failed.",
+               mEnabled ? "enabled" : "disabled");
   }
 
   mEnabled = aEnabled;
 }
 
 nsresult
 BluetoothService::HandleStartup()
 {
@@ -559,17 +553,17 @@ BluetoothService::HandleSettingsChanged(
     MOZ_ASSERT(!JS_IsExceptionPending(cx));
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (!key.isString()) {
     return NS_OK;
   }
 
-  // First, check if the string equals to BLUETOOTH_DEBUGGING_SETTING
+  // Check whether the string is BLUETOOTH_DEBUGGING_SETTING
   bool match;
   if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_DEBUGGING_SETTING, &match)) {
     MOZ_ASSERT(!JS_IsExceptionPending(cx));
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (match) {
     JS::Rooted<JS::Value> value(cx);
@@ -701,38 +695,16 @@ BluetoothService::Observe(nsISupports* a
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     return HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothService got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
 }
 
-void
-BluetoothService::TryFiringAdapterAdded()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (IsToggling() || !mAdapterAddedReceived) {
-    return;
-  }
-
-  BluetoothSignal signal(NS_LITERAL_STRING("AdapterAdded"),
-                         NS_LITERAL_STRING(KEY_MANAGER), true);
-  DistributeSignal(signal);
-}
-
-void
-BluetoothService::AdapterAddedReceived()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mAdapterAddedReceived = true;
-}
-
 /**
  * Enable/Disable the local adapter.
  *
  * There is only one adapter on the mobile in current use cases.
  * In addition, bluedroid couldn't enable/disable a single adapter.
  * So currently we will turn on/off BT to enable/disable the adapter.
  *
  * TODO: To support enable/disable single adapter in the future,
@@ -790,20 +762,18 @@ BluetoothService::Notify(const Bluetooth
     MOZ_ASSERT(aData.value().get_ArrayOfBluetoothNamedValue().Length() == 0,
       "Cancel: Wrong length of parameters");
     type.AssignLiteral("bluetooth-cancel");
   } else if (aData.name().EqualsLiteral(PAIRED_STATUS_CHANGED_ID)) {
     MOZ_ASSERT(aData.value().get_ArrayOfBluetoothNamedValue().Length() == 1,
       "pairedstatuschanged: Wrong length of parameters");
     type.AssignLiteral("bluetooth-pairedstatuschanged");
   } else {
-    nsCString warningMsg;
-    warningMsg.AssignLiteral("Not handling service signal: ");
-    warningMsg.Append(NS_ConvertUTF16toUTF8(aData.name()));
-    BT_WARNING(warningMsg.get());
+    BT_WARNING("Not handling service signal: %s",
+               NS_ConvertUTF16toUTF8(aData.name()).get());
     return;
   }
 
   nsCOMPtr<nsISystemMessagesInternal> systemMessenger =
     do_GetService("@mozilla.org/system-message-internal;1");
   NS_ENSURE_TRUE_VOID(systemMessenger);
 
   JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*obj));
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -303,23 +303,16 @@ public:
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
 
-  /**
-   * Below 2 function/variable are used for ensuring event 'AdapterAdded' will
-   * be fired after event 'Enabled'.
-   */
-  void TryFiringAdapterAdded();
-  void AdapterAddedReceived();
-
   void FireAdapterStateChanged(bool aEnable);
   nsresult EnableDisable(bool aEnable,
                          BluetoothReplyRunnable* aRunnable);
 
   /**
    * Platform specific startup functions go here. Usually deals with member
    * variables, so not static. Guaranteed to be called outside of main thread.
    *
@@ -334,19 +327,17 @@ public:
    *
    * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   StopInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
 protected:
   BluetoothService() : mEnabled(false)
-                     , mAdapterAddedReceived(false)
-  {
-  }
+  { }
 
   virtual ~BluetoothService();
 
   bool
   Init();
 
   void
   Cleanup();
@@ -395,16 +386,13 @@ protected:
   Create();
 
   typedef nsClassHashtable<nsStringHashKey, BluetoothSignalObserverList >
   BluetoothSignalObserverTable;
 
   BluetoothSignalObserverTable mBluetoothSignalObserverTable;
 
   bool mEnabled;
-
-private:
-  bool mAdapterAddedReceived;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -122,24 +122,16 @@ public:
 
     NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
 
     int ret = sBtInterface->set_adapter_property(&prop);
     if (ret != BT_STATUS_SUCCESS) {
       BT_LOGR("Fail to set: BT_SCAN_MODE_CONNECTABLE");
     }
 
-    // Try to fire event 'AdapterAdded' to fit the original behaviour when
-    // we used BlueZ as backend.
-    BluetoothService* bs = BluetoothService::Get();
-    NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
-
-    bs->AdapterAddedReceived();
-    bs->TryFiringAdapterAdded();
-
     // Trigger BluetoothOppManager to listen
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     if (!opp || !opp->Listen()) {
       BT_LOGR("Fail to start BluetoothOppManager listening");
     }
 
     return NS_OK;
   }
--- a/dom/bluetooth2/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.cpp
@@ -533,18 +533,20 @@ class TryFiringAdapterAddedTask : public
 public:
   void Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     BluetoothService* bs = BluetoothService::Get();
     NS_ENSURE_TRUE_VOID(bs);
 
+#if 0 // for API_V2
     bs->AdapterAddedReceived();
     bs->TryFiringAdapterAdded();
+#endif
   }
 };
 
 class TryFiringAdapterAddedRunnable : public nsRunnable
 {
 public:
   TryFiringAdapterAddedRunnable(bool aDelay)
     : mDelay(aDelay)
--- a/gfx/layers/ipc/SharedBufferManagerParent.cpp
+++ b/gfx/layers/ipc/SharedBufferManagerParent.cpp
@@ -13,71 +13,89 @@
 #include "base/thread.h"
 #include "mozilla/ipc/MessageChannel.h" // for MessageChannel, etc
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "nsIMemoryReporter.h"
 #ifdef MOZ_WIDGET_GONK
 #include "ui/PixelFormat.h"
 #endif
+#include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla::ipc;
 #ifdef MOZ_WIDGET_GONK
 using namespace android;
 #endif
 using std::map;
 
 namespace mozilla {
 namespace layers {
 
+map<base::ProcessId, SharedBufferManagerParent* > SharedBufferManagerParent::sManagers;
+StaticAutoPtr<Monitor> SharedBufferManagerParent::sManagerMonitor;
+Atomic<uint32_t> SharedBufferManagerParent::sBufferKey(0);
+
+#ifdef MOZ_WIDGET_GONK
 class GrallocReporter MOZ_FINAL : public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
 
-  GrallocReporter()
-  {
-#ifdef DEBUG
-    // There must be only one instance of this class, due to |sAmount|
-    // being static.  Assert this.
-    static bool hasRun = false;
-    MOZ_ASSERT(!hasRun);
-    hasRun = true;
-#endif
-  }
-
   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                             nsISupports* aData)
   {
-    return MOZ_COLLECT_REPORT(
-      "gralloc", KIND_OTHER, UNITS_BYTES, sAmount,
-"Special RAM that can be shared between processes and directly accessed by "
-"both the CPU and GPU. Gralloc memory is usually a relatively precious "
-"resource, with much less available than generic RAM. When it's exhausted, "
-"graphics performance can suffer. This value can be incorrect because of race "
-"conditions.");
+    map<base::ProcessId, SharedBufferManagerParent*>::iterator it;
+    for (it = SharedBufferManagerParent::sManagers.begin(); it != SharedBufferManagerParent::sManagers.end(); it++) {
+      base::ProcessId pid = it->first;
+      SharedBufferManagerParent *mgr = it->second;
+
+      std::map<int, android::sp<android::GraphicBuffer> >::iterator buf_it;
+      for (buf_it = mgr->mBuffers.begin(); buf_it != mgr->mBuffers.end(); buf_it++) {
+        nsresult rv;
+        android::sp<android::GraphicBuffer> gb = buf_it->second;
+        int bpp = android::bytesPerPixel(gb->getPixelFormat());
+        int stride = gb->getStride();
+        int height = gb->getHeight();
+        int amount = bpp > 0
+          ? (stride * height * bpp)
+          // Special case for BSP specific formats (mainly YUV formats, count it as normal YUV buffer).
+          : (stride * height * 3 / 2);
+
+        nsPrintfCString gpath("gralloc/pid(%d)/buffer(width=%d, height=%d, bpp=%d)",
+            pid, gb->getWidth(), height, bpp);
+
+        rv = aHandleReport->Callback(EmptyCString(), gpath, KIND_OTHER, UNITS_BYTES, amount,
+            NS_LITERAL_CSTRING(
+              "Special RAM that can be shared between processes and directly accessed by "
+              "both the CPU and GPU. Gralloc memory is usually a relatively precious "
+              "resource, with much less available than generic RAM. When it's exhausted, "
+              "graphics performance can suffer. This value can be incorrect because of race "
+              "conditions."),
+            aData);
+        if (rv != NS_OK) {
+          return rv;
+        }
+      }
+    }
+    return NS_OK;
   }
 
-  static int64_t sAmount;
 };
 
 NS_IMPL_ISUPPORTS(GrallocReporter, nsIMemoryReporter)
-
-int64_t GrallocReporter::sAmount = 0;
+#endif
 
 void InitGralloc() {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+#ifdef MOZ_WIDGET_GONK
   RegisterStrongMemoryReporter(new GrallocReporter());
+#endif
 }
 
-map<base::ProcessId, SharedBufferManagerParent* > SharedBufferManagerParent::sManagers;
-StaticAutoPtr<Monitor> SharedBufferManagerParent::sManagerMonitor;
-int SharedBufferManagerParent::sBufferKey = 0;
-
 SharedBufferManagerParent::SharedBufferManagerParent(Transport* aTransport, base::ProcessId aOwner, base::Thread* aThread)
   : mTransport(aTransport)
   , mThread(aThread)
 #ifdef MOZ_HAVE_SURFACEDESCRIPTORGRALLOC
   , mBuffersMutex("BuffersMonitor")
 #endif
 {
   if (!sManagerMonitor)
@@ -169,23 +187,16 @@ bool SharedBufferManagerParent::RecvAllo
     return true;
   }
 
   GrallocBufferRef ref;
   ref.mOwner = mOwner;
   ref.mKey = ++sBufferKey;
   *aHandle = MagicGrallocBufferHandle(outgoingBuffer, ref);
 
-  int bpp = 0;
-  bpp = android::bytesPerPixel(outgoingBuffer->getPixelFormat());
-  if (bpp > 0)
-    GrallocReporter::sAmount += outgoingBuffer->getStride() * outgoingBuffer->getHeight() * bpp;
-  else // Specical case for BSP specific formats(mainly YUV formats, count it as normal YUV buffer)
-    GrallocReporter::sAmount += outgoingBuffer->getStride() * outgoingBuffer->getHeight() * 3 / 2;
-
   {
     MutexAutoLock lock(mBuffersMutex);
     mBuffers[sBufferKey] = outgoingBuffer;
   }
 #endif
   return true;
 }
 
@@ -200,23 +211,16 @@ bool SharedBufferManagerParent::RecvDrop
   NS_ASSERTION(mBuffers.count(bufferKey) == 1, "No such buffer");
   mBuffers.erase(bufferKey);
 
   if(!buf.get()) {
     printf_stderr("SharedBufferManagerParent::RecvDropGrallocBuffer -- invalid buffer key.");
     return true;
   }
 
-  int bpp = 0;
-  bpp = android::bytesPerPixel(buf->getPixelFormat());
-  if (bpp > 0)
-    GrallocReporter::sAmount -= buf->getStride() * buf->getHeight() * bpp;
-  else // Specical case for BSP specific formats(mainly YUV formats, count it as normal YUV buffer)
-    GrallocReporter::sAmount -= buf->getStride() * buf->getHeight() * 3 / 2;
-
 #endif
   return true;
 }
 
 void SharedBufferManagerParent::DropGrallocBufferSync(SharedBufferManagerParent* mgr, mozilla::layers::SurfaceDescriptor aDesc)
 {
   mgr->DropGrallocBufferImpl(aDesc);
 }
--- a/gfx/layers/ipc/SharedBufferManagerParent.h
+++ b/gfx/layers/ipc/SharedBufferManagerParent.h
@@ -2,16 +2,17 @@
 /* vim: set sw=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef SharedBufferManagerPARENT_H_
 #define SharedBufferManagerPARENT_H_
 
+#include "mozilla/Atomics.h"          // for Atomic
 #include "mozilla/layers/PSharedBufferManagerParent.h"
 #include "mozilla/StaticPtr.h"
 
 #ifdef MOZ_HAVE_SURFACEDESCRIPTORGRALLOC
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/Mutex.h"            // for Mutex
 
 namespace android {
@@ -27,16 +28,17 @@ namespace mozilla {
 #ifdef MOZ_HAVE_SURFACEDESCRIPTORGRALLOC
 class Mutex;
 #endif
 
 namespace layers {
 
 class SharedBufferManagerParent : public PSharedBufferManagerParent
 {
+friend class GrallocReporter;
 public:
   /**
    * Create a SharedBufferManagerParent for child process, and link to the child side before leaving
    */
   static PSharedBufferManagerParent* Create(Transport* aTransport, ProcessId aOtherProcess);
 
   /**
    * Function for find the buffer owner, most buffer passing on IPC contains only owner/key pair.
@@ -95,15 +97,16 @@ protected:
    */
   std::map<int, android::sp<android::GraphicBuffer> > mBuffers;
   Mutex mBuffersMutex;
 #endif
   
   Transport* mTransport;
   base::ProcessId mOwner;
   base::Thread* mThread;
-  static int sBufferKey;
+  static mozilla::Atomic<uint32_t> sBufferKey;
+
   static StaticAutoPtr<Monitor> sManagerMonitor;
 };
 
 } /* namespace layers */
 } /* namespace mozilla */
 #endif /* SharedBufferManagerPARENT_H_ */
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -299,19 +299,16 @@ var SelectionHandler = {
    *                   y    - The y-coordinate for SELECT_AT_POINT.
    */
   startSelection: function sh_startSelection(aElement, aOptions = { mode: SelectionHandler.SELECT_ALL }) {
     // Clear out any existing active selection
     this._closeSelection();
 
     this._initTargetInfo(aElement, this.TYPE_SELECTION);
 
-    // Clear any existing selection from the document
-    this._contentWindow.getSelection().removeAllRanges();
-
     // Perform the appropriate selection method, if we can't determine method, or it fails, return
     if (!this._performSelection(aOptions)) {
       this._deactivate();
       return false;
     }
 
     // Double check results of successful selection operation
     let selection = this._getSelection();
@@ -399,17 +396,22 @@ var SelectionHandler = {
     }
 
     // HTMLPreElement is a #text node, SELECT_ALL implies entire paragraph
     if (this._targetElement instanceof HTMLPreElement)  {
       return this._domWinUtils.selectAtPoint(1, 1, Ci.nsIDOMWindowUtils.SELECT_PARAGRAPH);
     }
 
     // Else default to selectALL Document
-    this._getSelectionController().selectAll();
+    let editor = this._getEditor();
+    if (editor) {
+      editor.selectAll();
+    } else {
+      this._getSelectionController().selectAll();
+    }
 
     // Selection is entire HTMLHtmlElement, remove any trailing document whitespace
     let selection = this._getSelection();
     let lastNode = selection.focusNode;
     while (lastNode && lastNode.lastChild) {
       lastNode = lastNode.lastChild;
     }
 
@@ -710,16 +712,27 @@ var SelectionHandler = {
     if (this._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement) {
       return selection.QueryInterface(Ci.nsISelectionPrivate).
         toStringWithFormat("text/plain", Ci.nsIDocumentEncoder.OutputPreformatted | Ci.nsIDocumentEncoder.OutputRaw, 0);
     }
 
     return selection.toString().trim();
   },
 
+  _getEditor: function sh_getEditor() {
+    if (this._targetElement instanceof Ci.nsIDOMNSEditableElement) {
+      return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
+    }
+    return this._contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebNavigation)
+                              .QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIEditingSession)
+                              .getEditorForWindow(this._contentWindow);
+  },
+
   _getSelectionController: function sh_getSelectionController() {
     if (this._targetElement instanceof Ci.nsIDOMNSEditableElement)
       return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement).editor.selectionController;
     else
       return this._contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                                  getInterface(Ci.nsIWebNavigation).
                                  QueryInterface(Ci.nsIInterfaceRequestor).
                                  getInterface(Ci.nsISelectionDisplay).
--- a/toolkit/devtools/server/actors/memory.js
+++ b/toolkit/devtools/server/actors/memory.js
@@ -65,16 +65,23 @@ let MemoryActor = protocol.ActorClass({
       let url = this.tabActor.url;
       console.error("Error getting size of "+url);
     }
 
     return result;
   }, {
     request: {},
     response: RetVal("json"),
+  }),
+
+  residentUnique: method(function() {
+    return this._mgr.residentUnique;
+  }, {
+    request: {},
+    response: { value: RetVal("number") }
   })
 });
 
 exports.MemoryActor = MemoryActor;
 
 exports.MemoryFront = protocol.FrontClass(MemoryActor, {
   initialize: function(client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -12,17 +12,17 @@ interface nsISimpleEnumerator;
 
 /*
  * Memory reporters measure Firefox's memory usage.  They are primarily used to
  * generate the about:memory page.  You should read
  * https://wiki.mozilla.org/Memory_Reporting before writing a memory
  * reporter.
  */
 
-[scriptable, function, uuid(3a61be3b-b93b-461a-a4f8-388214f558b1)]
+[scriptable, function, uuid(62ef0e1c-dbd6-11e3-aa75-3c970e9f4238)]
 interface nsIMemoryReporterCallback : nsISupports
 {
   /*
    * The arguments to the callback are as follows.
    *
    *
    * |process|  The name of the process containing this reporter.  Each
    * reporter initially has "" in this field, indicating that it applies to the
@@ -306,16 +306,18 @@ interface nsIMemoryReporterManager : nsI
    * |resident| (UNITS_BYTES)  The resident size (a.k.a. RSS or physical memory
    * used).
    *
    * |residentFast| (UNITS_BYTES)  This is like |resident|, but on Mac OS
    * |resident| can purge pages, which is slow.  It also affects the result of
    * |residentFast|, and so |resident| and |residentFast| should not be used
    * together.
    *
+   * |residentUnique| (UNITS_BYTES)  The unique set size (a.k.a. USS).
+   *
    * |heapAllocated| (UNITS_BYTES)  Memory mapped by the heap allocator.
    *
    * |heapOverheadRatio| (UNITS_PERCENTAGE)  In the heap allocator, this is the
    * ratio of committed, unused bytes to allocated bytes.  Like all
    * UNITS_PERCENTAGE measurements, its amount is multiplied by 100x so it can
    * be represented by an int64_t.
    *
    * |JSMainRuntimeGCHeap| (UNITS_BYTES)  Size of the main JS runtime's GC
@@ -341,16 +343,17 @@ interface nsIMemoryReporterManager : nsI
    * |pageFaultsHard| (UNITS_COUNT_CUMULATIVE)  The number of hard (a.k.a.
    * major) page faults that have occurred since the process started.
    */
   readonly attribute int64_t explicit;
   readonly attribute int64_t vsize;
   readonly attribute int64_t vsizeMaxContiguous;
   readonly attribute int64_t resident;
   readonly attribute int64_t residentFast;
+  readonly attribute int64_t residentUnique;
 
   readonly attribute int64_t heapAllocated;
   readonly attribute int64_t heapOverheadRatio;
 
   readonly attribute int64_t JSMainRuntimeGCHeap;
   readonly attribute int64_t JSMainRuntimeTemporaryPeak;
   readonly attribute int64_t JSMainRuntimeCompartmentsSystem;
   readonly attribute int64_t JSMainRuntimeCompartmentsUser;
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -105,26 +105,32 @@ ResidentDistinguishedAmount(int64_t* aN)
 
 static nsresult
 ResidentFastDistinguishedAmount(int64_t* aN)
 {
   return ResidentDistinguishedAmount(aN);
 }
 
 #define HAVE_RESIDENT_UNIQUE_REPORTER
+static nsresult
+ResidentUniqueDistinguishedAmount(int64_t* aN)
+{
+  return GetProcSelfSmapsPrivate(aN);
+}
+
 class ResidentUniqueReporter MOZ_FINAL : public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
 
   NS_METHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                            nsISupports* aData)
   {
     int64_t amount = 0;
-    nsresult rv = GetProcSelfSmapsPrivate(&amount);
+    nsresult rv = ResidentUniqueDistinguishedAmount(&amount);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return MOZ_COLLECT_REPORT(
       "resident-unique", KIND_OTHER, UNITS_BYTES, amount,
 "Memory mapped by the process that is present in physical memory and not "
 "shared with any other processes.  This is also known as the process's unique "
 "set size (USS).  This is the amount of RAM we'd expect to be freed if we "
 "closed this process.");
@@ -1551,26 +1557,39 @@ nsMemoryReporterManager::ResidentFast()
   nsresult rv = ResidentFastDistinguishedAmount(&amount);
   NS_ENSURE_SUCCESS(rv, 0);
   return amount;
 #else
   return 0;
 #endif
 }
 
-#if defined(XP_LINUX)
+NS_IMETHODIMP
+nsMemoryReporterManager::GetResidentUnique(int64_t* aAmount)
+{
+#ifdef HAVE_RESIDENT_UNIQUE_REPORTER
+  return ResidentUniqueDistinguishedAmount(aAmount);
+#else
+  *aAmount = 0;
+  return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
 /*static*/ int64_t
 nsMemoryReporterManager::ResidentUnique()
 {
+#ifdef HAVE_RESIDENT_UNIQUE_REPORTER
   int64_t amount = 0;
-  nsresult rv = GetProcSelfSmapsPrivate(&amount);
+  nsresult rv = ResidentUniqueDistinguishedAmount(&amount);
   NS_ENSURE_SUCCESS(rv, 0);
   return amount;
+#else
+  return 0;
+#endif
 }
-#endif
 
 NS_IMETHODIMP
 nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount)
 {
 #ifdef HAVE_JEMALLOC_STATS
   jemalloc_stats_t stats;
   jemalloc_stats(&stats);
   *aAmount = stats.allocated;
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -145,21 +145,19 @@ public:
     }
   };
   AmountFns mAmountFns;
 
   // Convenience function to get RSS easily from other code.  This is useful
   // when debugging transient memory spikes with printf instrumentation.
   static int64_t ResidentFast();
 
-#if defined(XP_LINUX)
   // Convenience function to get USS easily from other code.  This is useful
   // when debugging unshared memory pages for forked processes.
   static int64_t ResidentUnique();
-#endif
 
   // Functions that measure per-tab memory consumption.
   struct SizeOfTabFns
   {
     mozilla::JSSizeOfTabFn    mJS;
     mozilla::NonJSSizeOfTabFn mNonJS;
 
     SizeOfTabFns()