Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 15 Sep 2015 15:10:11 +0200
changeset 262595 15f2e2c86ca9f9640cd9bc5ac5546f7441ef01e2
parent 262594 05202b078d10b15e05b6f1c7151446ac0ca10b02 (current diff)
parent 262563 5d61714cf5c3ec2440e9491ba30893ac37ca06c2 (diff)
child 262596 db3691ee6fd1adfbf19ee6e18637956eaf87b7f1
push id15198
push userkwierso@gmail.com
push dateWed, 16 Sep 2015 00:21:31 +0000
treeherderfx-team@dbfddd6efb5d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone43.0a1
Merge mozilla-central to mozilla-inbound
mobile/android/base/resources/drawable-hdpi/menu_light.png
mobile/android/base/resources/drawable-xhdpi/menu_light.png
mobile/android/modules/MatchstickApp.jsm
modules/libpref/init/all.js
--- a/b2g/chrome/content/devtools/hud.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -760,66 +760,100 @@ developerHUD.registerWatcher(eventLoopLa
  * to the app-launch epoch and emits an "app-start-time-<performance mark name>"
  * event containing the delta.
  */
 let performanceEntriesWatcher = {
   _client: null,
   _fronts: new Map(),
   _appLaunchName: null,
   _appLaunchStartTime: null,
+  _supported: [
+    'contentInteractive',
+    'navigationInteractive',
+    'navigationLoaded',
+    'visuallyLoaded',
+    'fullyLoaded',
+    'mediaEnumerated',
+    'scanEnd'
+  ],
 
   init(client) {
     this._client = client;
+    let setting = 'devtools.telemetry.supported_performance_marks';
+    let defaultValue = this._supported.join(',');
+
+    SettingsListener.observe(setting, defaultValue, supported => {
+      let value = supported || defaultValue;
+      this._supported = value.split(',');
+    });
   },
 
   trackTarget(target) {
     // The performanceEntries watcher doesn't register a metric because
     // currently the metrics generated are not displayed in
     // in the front-end.
 
     let front = new PerformanceEntriesFront(this._client, target.actor);
     this._fronts.set(target, front);
 
     // User timings are always gathered; there is no setting to enable/
     // disable.
     front.start();
 
     front.on('entry', detail => {
-      if (detail.type === 'mark') {
-        let name = detail.name;
-        let epoch = detail.epoch;
+
+      // Only process performance marks.
+      if (detail.type !== 'mark') {
+        return;
+      }
+
+      let name = detail.name;
+      let epoch = detail.epoch;
+
+      // FIXME There is a potential race condition that can result
+      // in some performance entries being disregarded. See bug 1189942.
+      //
+      // If this is an "app launch" mark, record the app that was
+      // launched and the epoch of when it was launched.
+      if (name.indexOf('appLaunch') !== -1) {
         let CHARS_UNTIL_APP_NAME = 7; // '@app://'
+        let startPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
+        let endPos = name.indexOf('.');
+        this._appLaunchName = name.slice(startPos, endPos);
+        this._appLaunchStartTime = epoch;
+        return;
+      }
 
-        // FIXME There is a potential race condition that can result
-        // in some performance entries being disregarded. See bug 1189942.
-        if (name.indexOf('appLaunch') != -1) {
-          let appStartPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
-          let length = (name.indexOf('.') - appStartPos);
-          this._appLaunchName = name.substr(appStartPos, length);
-          this._appLaunchStartTime = epoch;
-        } else {
-          let origin = detail.origin;
-          origin = origin.substr(0, origin.indexOf('.'));
-          if (this._appLaunchName === origin) {
-            let time = epoch - this._appLaunchStartTime;
-            let eventName = 'app-startup-time-' + name;
+      // Only process supported performance marks
+      if (this._supported.indexOf(name) === -1) {
+        return;
+      }
+
+      let origin = detail.origin;
+      origin = origin.slice(0, origin.indexOf('.'));
+
+      // Continue if the performance mark corresponds to the app
+      // for which we have recorded app launch information.
+      if (this._appLaunchName !== origin) {
+        return;
+      }
 
-            // Events based on performance marks are for telemetry only, they are
-            // not displayed in the HUD front end.
-            target._logHistogram({name: eventName, value: time});
+      let time = epoch - this._appLaunchStartTime;
+      let eventName = 'app_startup_time_' + name;
+
+      // Events based on performance marks are for telemetry only, they are
+      // not displayed in the HUD front end.
+      target._logHistogram({name: eventName, value: time});
 
-            memoryWatcher.front(target).residentUnique().then(value => {
-              eventName = 'app-memory-' + name;
-              target._logHistogram({name: eventName, value: value});
-            }, err => {
-              console.error(err);
-            });
-          }
-        }
-      }
+      memoryWatcher.front(target).residentUnique().then(value => {
+        eventName = 'app_memory_' + name;
+        target._logHistogram({name: eventName, value: value});
+      }, err => {
+        console.error(err);
+      });
     });
   },
 
   untrackTarget(target) {
     let fronts = this._fronts;
     if (fronts.has(target)) {
       fronts.get(target).destroy();
       fronts.delete(target);
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -568,16 +568,20 @@ let settingsToObserve = {
   'devtools.discovery.device': {
     prefName: 'dom.presentation.device.name',
     defaultValue: 'Firefox OS'
   },
   'devtools.eventlooplag.threshold': 100,
   'devtools.remote.wifi.visible': {
     resetToPref: true
   },
+  'devtools.telemetry.supported_performance_marks': {
+    resetToPref: true
+  },
+
   'dom.mozApps.use_reviewer_certs': false,
   'dom.mozApps.signed_apps_installable_from': 'https://marketplace.firefox.com',
   'dom.presentation.discovery.enabled': false,
   'dom.presentation.discoverable': false,
   'dom.serviceWorkers.interception.enabled': true,
   'dom.serviceWorkers.testing.enabled': false,
   'gfx.layerscope.enabled': false,
   'layers.draw-borders': false,
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,25 +10,25 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,25 +10,25 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
   <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/>
--- 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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="067c08fb3e5744b42b68d1f861245f7d507109bc"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- 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,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,25 +10,25 @@
   <!--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="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
   <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="24b2038be8a636fd4a5d21f0abae1e466b07bcf7"/>
--- 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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="067c08fb3e5744b42b68d1f861245f7d507109bc"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "e6994410dcc88bfead0e44620a065220b7f12290", 
+        "git_revision": "d2e5c49440bf8410ae747b15c0dd11c54053ef3e", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "0611acb452ed9c34d02ccdf24850677490b716d1", 
+    "revision": "4b08f8a2624bc83d1f839f79b4969671417c42d6", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,25 +10,25 @@
   <!--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="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e6994410dcc88bfead0e44620a065220b7f12290"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="d2e5c49440bf8410ae747b15c0dd11c54053ef3e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="f5d65f5b17d9766d7925aefd0486a1e526ae9bf0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="c6cace53426b5be7e56c0fd202118009689bc707"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="fb28def18ceb2516c460c4bd5825d2dc656c7818"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
   <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="24b2038be8a636fd4a5d21f0abae1e466b07bcf7"/>
--- a/browser/base/content/test/newtab/browser_newtab_block.js
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -8,16 +8,17 @@
  */
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "suggested": [{
     url: "http://suggested.com/",
     imageURI: "data:image/png;base64,helloWORLD3",
     title: "title",
     type: "affiliate",
+    adgroup_name: "test",
     frecent_sites: ["example0.com"]
   }]
 });
 
 function runTests() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
--- a/browser/base/content/test/newtab/browser_newtab_bug1145428.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
@@ -10,16 +10,17 @@
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "suggested": [{
     url: "http://example.com/landing/page.html",
     imageURI: "data:image/png;base64,helloWORLD3",
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title",
     type: "affiliate",
+    adgroup_name: "example",
     frecent_sites: ["example0.com"],
   }]
 });
 
 function runTests() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
--- a/browser/base/content/test/newtab/browser_newtab_bug1178586.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
@@ -8,16 +8,17 @@
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "suggested": [{
     url: "http://example.com/hardlanding/page.html",
     imageURI: "data:image/png;base64,helloWORLD3",
     enhancedImageURI: "data:image/png;base64,helloWORLD2",
     title: "title",
     type: "affiliate",
+    adgroup_name: "example",
     frecent_sites: ["example0.com"],
   }]
 });
 
 function runTests() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -3,16 +3,17 @@
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 let suggestedLink = {
   url: "http://example1.com/2",
   imageURI: "data:image/png;base64,helloWORLD3",
   title: "title2",
   type: "affiliate",
+  adgroup_name: "Technology",
   frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"]
 };
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "enhanced": [{
     url: "http://example.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD",
     title: "title",
@@ -134,31 +135,30 @@ function runTests() {
   yield addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
 
   // Suggested link was not enhanced by directory link with same domain
   ({type, enhanced, title, suggested} = getData(0));
   is(type, "affiliate", "suggested link is affiliate");
   is(enhanced, "", "suggested link has no enhanced image");
   is(title, "title2");
-  ok(suggested.indexOf("Suggested for <strong> webdev education </strong> visitors") > -1, "Suggested for 'webdev education'");
+  ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
 
   // Enhanced history link shows up second
   ({type, enhanced, title, suggested} = getData(1));
   is(type, "enhanced", "pinned history link is enhanced");
   isnot(enhanced, "", "pinned history link has enhanced image");
   is(title, "title");
   is(suggested, "", "There is no suggested explanation");
 
   is(getData(9), null, "there is a suggested link followed by an enhanced history link and the remaining history links");
 
 
 
-  // Test override category/adgroup name.
-  suggestedLink.adgroup_name = "Technology";
+  // Test no override category/adgroup name.
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
     "data:application/json," + JSON.stringify({"suggested": [suggestedLink]}));
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
@@ -171,18 +171,18 @@ function runTests() {
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
 
 
-  // Test server provided explanation string without category override.
-  delete suggestedLink.adgroup_name;
+  // Test server provided explanation string with category override.
+  suggestedLink.adgroup_name = "webdev education";
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
     "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
   yield watchLinksChangeOnce().then(TestRunner.next);
 
   yield addNewTabPageTab();
   ({type, enhanced, title, suggested} = getData(0));
   Cu.reportError("SUGGEST " + suggested);
   ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -55,17 +55,17 @@ extensions.registerAPI((extension, conte
           callback = args[0];
         } else {
           [getInfo, callback] = args;
         }
         let window = WindowManager.topWindow;
         runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
       },
 
-      getAll: function(getAll, callback) {
+      getAll: function(getInfo, callback) {
         let e = Services.wm.getEnumerator("navigator:browser");
         let windows = [];
         while (e.hasMoreElements()) {
           let window = e.getNext();
           windows.push(WindowManager.convert(extension, window, getInfo));
         }
         runSafe(context, callback, windows);
       },
@@ -126,17 +126,20 @@ extensions.registerAPI((extension, conte
       },
 
       update: function(windowId, updateInfo, callback) {
         let window = WindowManager.getWindow(windowId);
         if (updateInfo.focused) {
           Services.focus.activeWindow = window;
         }
         // TODO: All the other properties...
-        runSafe(context, callback, WindowManager.convert(extension, window));
+
+        if (callback) {
+          runSafe(context, callback, WindowManager.convert(extension, window));
+        }
       },
 
       remove: function(windowId, callback) {
         let window = WindowManager.getWindow(windowId);
         window.close();
 
         let listener = () => {
           AllWindowEvents.removeListener("domwindowclosed", listener);
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac'
 
 [browser_extensions_simple.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_update.js]
+[browser_ext_windows_update.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -0,0 +1,51 @@
+add_task(function* () {
+  function promiseWaitForFocus(aWindow) {
+    return new Promise(function(aResolve, aReject) {
+      waitForFocus(function() {
+        ok(Services.focus.activeWindow === aWindow, "correct window focused");
+        aResolve();
+      }, aWindow);
+    });
+  }
+
+  let window1 = window;
+  let window2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  Services.focus.activeWindow = window2;
+  yield promiseWaitForFocus(window2);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["windows"]
+    },
+
+    background: function() {
+      browser.windows.getAll(undefined, function(wins) {
+        browser.test.assertEq(wins.length, 2, "should have two windows");
+
+        // Sort the unfocused window to the lower index.
+        wins.sort(function(win1, win2) {
+          if (win1.focused === win2.focused) {
+            return 0;
+          }
+
+          return win1.focused ? 1 : -1;
+        });
+
+        browser.windows.update(wins[0].id, {focused: true}, function() {
+          browser.test.sendMessage("check");
+        });
+
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitMessage("check");
+
+  yield promiseWaitForFocus(window1);
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.closeWindow(window2);
+});
\ No newline at end of file
--- a/browser/devtools/canvasdebugger/snapshotslist.js
+++ b/browser/devtools/canvasdebugger/snapshotslist.js
@@ -415,24 +415,25 @@ let SnapshotsListView = Heritage.extend(
         screenshot: null
       };
       let functionCalls = snapshotItem.attachment.calls;
       let thumbnails = snapshotItem.attachment.thumbnails;
       let screenshot = snapshotItem.attachment.screenshot;
 
       // Prepare all the function calls for serialization.
       yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => {
-        let { type, name, file, line, argsPreview, callerPreview } = call;
+        let { type, name, file, line, timestamp, argsPreview, callerPreview } = call;
         return call.getDetails().then(({ stack }) => {
           data.calls[i] = {
             type: type,
             name: name,
             file: file,
             line: line,
             stack: stack,
+            timestamp: timestamp,
             argsPreview: argsPreview,
             callerPreview: callerPreview
           };
         });
       });
 
       // Prepare all the thumbnails for serialization.
       yield DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => {
--- a/browser/devtools/canvasdebugger/test/browser.ini
+++ b/browser/devtools/canvasdebugger/test/browser.ini
@@ -47,8 +47,10 @@ skip-if = e10s # bug 1102301 - leaks whi
 [browser_canvas-frontend-slider-01.js]
 [browser_canvas-frontend-slider-02.js]
 [browser_canvas-frontend-snapshot-select-01.js]
 [browser_canvas-frontend-snapshot-select-02.js]
 [browser_canvas-frontend-stepping.js]
 [browser_canvas-frontend-stop-01.js]
 [browser_canvas-frontend-stop-02.js]
 [browser_canvas-frontend-stop-03.js]
+[browser_profiling-canvas.js]
+[browser_profiling-webgl.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/canvasdebugger/test/browser_profiling-canvas.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context profiling.
+ */
+
+function* ifTestingSupported() {
+  let currentTime = window.performance.now();
+  let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
+
+  let navigated = once(target, "navigate");
+
+  yield front.setup({ reload: true });
+  ok(true, "The front was setup up successfully.");
+
+  yield navigated;
+  ok(true, "Target automatically navigated when the front was set up.");
+
+  let snapshotActor = yield front.recordAnimationFrame();
+  ok(snapshotActor,
+    "A snapshot actor was sent after recording.");
+
+  let animationOverview = yield snapshotActor.getOverview();
+  ok(animationOverview,
+    "An animation overview could be retrieved after recording.");
+
+  let functionCalls = animationOverview.calls;
+  ok(functionCalls,
+    "An array of function call actors was sent after recording.");
+  is(functionCalls.length, 8,
+    "The number of function call actors is correct.");
+
+  info("Check the timestamps of function calls");
+
+  for ( let i = 0; i < functionCalls.length-1; i += 2 ) {
+    ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." );
+    ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." );
+    ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." );
+  }
+
+  yield removeTab(target.tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/canvasdebugger/test/browser_profiling-webgl.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if functions inside a single animation frame are recorded and stored
+ * for a canvas context profiling.
+ */
+
+function* ifTestingSupported() {
+  let currentTime = window.performance.now();
+  let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL);
+
+  let navigated = once(target, "navigate");
+
+  yield front.setup({ reload: true });
+  ok(true, "The front was setup up successfully.");
+
+  yield navigated;
+  ok(true, "Target automatically navigated when the front was set up.");
+
+  let snapshotActor = yield front.recordAnimationFrame();
+  ok(snapshotActor,
+    "A snapshot actor was sent after recording.");
+
+  let animationOverview = yield snapshotActor.getOverview();
+  ok(animationOverview,
+    "An animation overview could be retrieved after recording.");
+
+  let functionCalls = animationOverview.calls;
+  ok(functionCalls,
+    "An array of function call actors was sent after recording.");
+  is(functionCalls.length, 3,
+    "The number of function call actors is correct.");
+
+  info("Check the timestamps of function calls");
+
+  for ( let i = 0; i < functionCalls.length-1; i += 2 ) {
+    ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." );
+    ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." );
+    ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." );
+  }
+
+  yield removeTab(target.tab);
+  finish();
+}
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -18,16 +18,17 @@ support-files =
   doc_markup_not_displayed.html
   doc_markup_pagesize_01.html
   doc_markup_pagesize_02.html
   doc_markup_search.html
   doc_markup_svg_attributes.html
   doc_markup_toggle.html
   doc_markup_tooltip.png
   doc_markup_xul.xul
+  frame-script-utils.js
   head.js
   helper_attributes_test_runner.js
   helper_events_test_runner.js
   helper_outerhtml_test_runner.js
   lib_jquery_1.0.js
   lib_jquery_1.1.js
   lib_jquery_1.2_min.js
   lib_jquery_1.3_min.js
--- a/browser/devtools/markupview/test/browser_markupview_keybindings_04.js
+++ b/browser/devtools/markupview/test/browser_markupview_keybindings_04.js
@@ -40,20 +40,38 @@ add_task(function*() {
 });
 
 function assertNodeSelected(inspector, tagName) {
   is(inspector.selection.nodeFront.tagName.toLowerCase(), tagName,
     `The <${tagName}> node is selected`);
 }
 
 function* selectWithBrowserMenu(inspector) {
-  yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
-    type: "contextmenu",
-    button: 2
-  }, gBrowser.selectedBrowser);
+  // This test can't use BrowserTestUtils.synthesizeMouseAtCenter()
+  // method (see below) since it causes intermittent test failures.
+  // So, we are introducing a new "Test:MarkupView:SynthesizeMouse" event
+  // that is handled in the content scope. The main difference between
+  // this new event and BrowserTestUtils library is EventUtils library.
+  // While BrowserTestUtils is using:
+  // chrome://mochikit/content/tests/SimpleTest/EventUtils.js
+  // (see: AsyncUtilsContent.js)
+  // ... this test requires:
+  // chrome://marionette/content/EventUtils.js
+  // (see markupview/test/frame-script-utils.js)
+  // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1199180
+  yield executeInContent("Test:MarkupView:SynthesizeMouse", {
+    center: true,
+    selector: "div",
+    options: {type: "contextmenu", button: 2}
+  });
+
+  //yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
+  //  type: "contextmenu",
+  //  button: 2
+  //}, gBrowser.selectedBrowser);
 
   // nsContextMenu also requires the popupNode to be set, but we can't set it to
   // node under e10s as it's a CPOW, not a DOM node. But under e10s,
   // nsContextMenu won't use the property anyway, so just try/catching is ok.
   try {
     document.popupNode = getNode("div");
   } catch (e) {}
 
@@ -65,17 +83,26 @@ function* selectWithBrowserMenu(inspecto
   contentAreaContextMenu.hidePopup();
   contextMenu.hiding();
 
   yield inspector.once("inspector-updated");
 }
 
 function* selectWithElementPicker(inspector) {
   yield inspector.toolbox.highlighterUtils.startPicker();
-  yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
-    type: "mousemove",
-  }, gBrowser.selectedBrowser);
+
+  yield executeInContent("Test:MarkupView:SynthesizeMouse", {
+    center: true,
+    selector: "div",
+    options: {type: "mousemove"}
+  });
+
+  // Read comment in selectWithBrowserMenu() method.
+  //yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
+  //  type: "mousemove",
+  //}, gBrowser.selectedBrowser);
+
   executeInContent("Test:SynthesizeKey", {
     key: "VK_RETURN",
     options: {}
   }, false);
   yield inspector.once("inspector-updated");
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/frame-script-utils.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci} = Components;
+const subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+                          .getService(Ci.mozIJSSubScriptLoader);
+let EventUtils = {};
+subScriptLoader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
+
+/**
+ * Synthesize a mouse event on an element. This handler doesn't send a message
+ * back. Consumers should listen to specific events on the inspector/highlighter
+ * to know when the event got synthesized.
+ * @param {Object} msg The msg.data part expects the following properties:
+ * - {Number} x
+ * - {Number} y
+ * - {Boolean} center If set to true, x/y will be ignored and
+ *             synthesizeMouseAtCenter will be used instead
+ * - {Object} options Other event options
+ * - {String} selector An optional selector that will be used to find the node to
+ *            synthesize the event on, if msg.objects doesn't contain the CPOW.
+ * The msg.objects part should be the element.
+ * @param {Object} data Event detail properties:
+ */
+addMessageListener("Test:MarkupView:SynthesizeMouse", function(msg) {
+  let {x, y, center, options, selector} = msg.data;
+  let {node} = msg.objects;
+
+  if (!node && selector) {
+    node = content.document.querySelector(selector);
+  }
+
+  if (center) {
+    EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView);
+  } else {
+    EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView);
+  }
+
+  // Most consumers won't need to listen to this message, unless they want to
+  // wait for the mouse event to be synthesized and don't have another event
+  // to listen to instead.
+  sendAsyncMessage("Test:MarkupView:SynthesizeMouse");
+});
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -46,16 +46,17 @@ registerCleanupFunction(function*() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 const TEST_URL_ROOT = "http://mochi.test:8888/browser/browser/devtools/markupview/test/";
 const CHROME_BASE = "chrome://mochitests/content/browser/browser/devtools/markupview/test/";
 const COMMON_FRAME_SCRIPT_URL = "chrome://browser/content/devtools/frame-script-utils.js";
+const MARKUPVIEW_FRAME_SCRIPT_URL = CHROME_BASE + "frame-script-utils.js";
 
 /**
  * Add a new test tab in the browser and load the given url.
  * @param {String} url The url to be loaded in the new tab
  * @return a promise that resolves to the tab object when the url is loaded
  */
 function addTab(url) {
   info("Adding a new tab with URL: '" + url + "'");
@@ -66,16 +67,17 @@ function addTab(url) {
   // For now, we just make sure the window is focused.
   window.focus();
 
   let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
   let linkedBrowser = tab.linkedBrowser;
 
   info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
   linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
+  linkedBrowser.messageManager.loadFrameScript(MARKUPVIEW_FRAME_SCRIPT_URL, false);
 
   linkedBrowser.addEventListener("load", function onload() {
     linkedBrowser.removeEventListener("load", onload, true);
     info("URL '" + url + "' loading complete");
     def.resolve(tab);
   }, true);
 
   return def.promise;
--- a/browser/devtools/performance/modules/logic/frame-utils.js
+++ b/browser/devtools/performance/modules/logic/frame-utils.js
@@ -549,13 +549,37 @@ function getFrameInfo (node, options) {
     data.totalSizePercentage = node.byteSize / totalBytes * 100;
     data.ALLOCATION_DATA_CALCULATED = true;
   }
 
   return data;
 }
 
 exports.getFrameInfo = getFrameInfo;
+
+/**
+ * Takes an inverted ThreadNode and searches its youngest frames for
+ * a FrameNode with matching location.
+ *
+ * @param {ThreadNode} threadNode
+ * @param {string} location
+ * @return {?FrameNode}
+ */
+function findFrameByLocation (threadNode, location) {
+  if (!threadNode.inverted) {
+    throw new Error("FrameUtils.findFrameByLocation only supports leaf nodes in an inverted tree.");
+  }
+
+  let calls = threadNode.calls;
+  for (let i = 0; i < calls.length; i++) {
+    if (calls[i].location === location) {
+      return calls[i];
+    }
+  }
+  return null;
+}
+
+exports.findFrameByLocation = findFrameByLocation;
 exports.computeIsContentAndCategory = computeIsContentAndCategory;
 exports.parseLocation = parseLocation;
 exports.getInflatedFrameCache = getInflatedFrameCache;
 exports.getOrAddInflatedFrame = getOrAddInflatedFrame;
 exports.InflatedFrame = InflatedFrame;
--- a/browser/devtools/performance/modules/logic/jit.js
+++ b/browser/devtools/performance/modules/logic/jit.js
@@ -264,70 +264,72 @@ const IMPLEMENTATION_NAMES = Object.keys
  * Takes data from a FrameNode and computes rendering positions for
  * a stacked mountain graph, to visualize JIT optimization tiers over time.
  *
  * @param {FrameNode} frameNode
  *                    The FrameNode who's optimizations we're iterating.
  * @param {Array<number>} sampleTimes
  *                        An array of every sample time within the range we're counting.
  *                        From a ThreadNode's `sampleTimes` property.
- * @param {number} op.startTime
- *                 The start time of the first sample.
- * @param {number} op.endTime
- *                 The end time of the last sample.
- * @param {number} op.resolution
- *                 The maximum amount of possible data points returned.
- *                 Also determines the size in milliseconds of each bucket
- *                 via `(endTime - startTime) / resolution`
+ * @param {number} bucketSize
+ *                 Size of each bucket in milliseconds.
+ *                 `duration / resolution = bucketSize` in OptimizationsGraph.
  * @return {?Array<object>}
  */
-function createTierGraphDataFromFrameNode (frameNode, sampleTimes, { startTime, endTime, resolution }) {
-  if (!frameNode.hasOptimizations()) {
-    return;
-  }
-
-  let tierData = frameNode.getOptimizationTierData();
-  let duration = endTime - startTime;
+function createTierGraphDataFromFrameNode (frameNode, sampleTimes, bucketSize) {
+  let tierData = frameNode.getTierData();
   let stringTable = frameNode._stringTable;
   let output = [];
   let implEnum;
 
   let tierDataIndex = 0;
   let nextOptSample = tierData[tierDataIndex];
 
   // Bucket data
   let samplesInCurrentBucket = 0;
   let currentBucketStartTime = sampleTimes[0];
   let bucket = [];
-  // Size of each bucket in milliseconds
-  let bucketSize = Math.ceil(duration / resolution);
+
+  // Store previous data point so we can have straight vertical lines
+  let previousValues;
 
   // Iterate one after the samples, so we can finalize the last bucket
   for (let i = 0; i <= sampleTimes.length; i++) {
     let sampleTime = sampleTimes[i];
 
     // If this sample is in the next bucket, or we're done
     // checking sampleTimes and on the last iteration, finalize previous bucket
     if (sampleTime >= (currentBucketStartTime + bucketSize) ||
         i >= sampleTimes.length) {
 
       let dataPoint = {};
-      dataPoint.ys = [];
-      dataPoint.x = currentBucketStartTime;
+      dataPoint.values = [];
+      dataPoint.delta = currentBucketStartTime;
 
       // Map the opt site counts as a normalized percentage (0-1)
       // of its count in context of total samples this bucket
       for (let j = 0; j < IMPLEMENTATION_NAMES.length; j++) {
-        dataPoint.ys[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
+        dataPoint.values[j] = (bucket[j] || 0) / (samplesInCurrentBucket || 1);
       }
+
+      // Push the values from the previous bucket to the same time
+      // as the current bucket so we get a straight vertical line.
+      if (previousValues) {
+        let data = Object.create(null);
+        data.values = previousValues;
+        data.delta = currentBucketStartTime;
+        output.push(data);
+      }
+
       output.push(dataPoint);
 
       // Set the new start time of this bucket and reset its count
       currentBucketStartTime += bucketSize;
       samplesInCurrentBucket = 0;
+      previousValues = dataPoint.values;
       bucket = [];
     }
 
     // If this sample observed an optimization in this frame, record it
     if (nextOptSample && nextOptSample.time === sampleTime) {
       // If no implementation defined, it was the "interpreter".
       implEnum = IMPLEMENTATION_MAP[stringTable[nextOptSample.implementation] || "interpreter"];
       bucket[implEnum] = (bucket[implEnum] || 0) + 1;
--- a/browser/devtools/performance/modules/logic/tree-model.js
+++ b/browser/devtools/performance/modules/logic/tree-model.js
@@ -31,16 +31,18 @@ function ThreadNode(thread, options = {}
     throw new Error("ThreadNode requires both `startTime` and `endTime`.");
   }
   this.samples = 0;
   this.sampleTimes = [];
   this.youngestFrameSamples = 0;
   this.calls = [];
   this.duration = options.endTime - options.startTime;
   this.nodeType = "Thread";
+  this.inverted = options.invertTree;
+
   // Total bytesize of all allocations if enabled
   this.byteSize = 0;
   this.youngestFrameByteSize = 0;
 
   let { samples, stackTable, frameTable, stringTable } = thread;
 
   // Nothing to do if there are no samples.
   if (samples.data.length === 0) {
@@ -227,20 +229,18 @@ ThreadNode.prototype = {
           calls = prevCalls;
         }
 
         let frameNode = getOrAddFrameNode(calls, isLeaf, frameKey, inflatedFrame,
                                           mutableFrameKeyOptions.isMetaCategoryOut,
                                           leafTable);
         if (isLeaf) {
           frameNode.youngestFrameSamples++;
-          if (inflatedFrame.optimizations) {
-            frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation,
-                                        sampleTime, stringTable);
-          }
+          frameNode._addOptimizations(inflatedFrame.optimizations, inflatedFrame.implementation,
+                                      sampleTime, stringTable);
 
           if (byteSize) {
             frameNode.youngestFrameByteSize += byteSize;
           }
         }
 
         // Don't overcount flattened recursive frames.
         if (!shouldFlatten) {
@@ -405,57 +405,57 @@ function FrameNode(frameKey, { location,
   this.key = frameKey;
   this.location = location;
   this.line = line;
   this.youngestFrameSamples = 0;
   this.samples = 0;
   this.calls = [];
   this.isContent = !!isContent;
   this._optimizations = null;
-  this._tierData = null;
+  this._tierData = [];
   this._stringTable = null;
   this.isMetaCategory = !!isMetaCategory;
   this.category = category;
   this.nodeType = "Frame";
   this.byteSize = 0;
   this.youngestFrameByteSize = 0;
 }
 
 FrameNode.prototype = {
   /**
    * Take optimization data observed for this frame.
    *
    * @param object optimizationSite
    *               Any JIT optimization information attached to the current
    *               sample. Lazily inflated via stringTable.
    * @param number implementation
-   *               JIT implementation used for this observed frame (interpreter,
-   *               baseline, ion);
+   *               JIT implementation used for this observed frame (baseline, ion);
+   *               can be null indicating "interpreter"
    * @param number time
    *               The time this optimization occurred.
    * @param object stringTable
    *               The string table used to inflate the optimizationSite.
    */
   _addOptimizations: function (site, implementation, time, stringTable) {
     // Simply accumulate optimization sites for now. Processing is done lazily
     // by JITOptimizations, if optimization information is actually displayed.
     if (site) {
       let opts = this._optimizations;
       if (opts === null) {
         opts = this._optimizations = [];
-        this._stringTable = stringTable;
       }
       opts.push(site);
+    }
 
-      if (this._tierData === null) {
-        this._tierData = [];
-      }
-      // Record type of implementation used and the sample time
-      this._tierData.push({ implementation, time });
+    if (!this._stringTable) {
+      this._stringTable = stringTable;
     }
+
+    // Record type of implementation used and the sample time
+    this._tierData.push({ implementation, time });
   },
 
   _clone: function (samples, size) {
     let newNode = new FrameNode(this.key, this, this.isMetaCategory);
     newNode._merge(this, samples, size);
     return newNode;
   },
 
@@ -469,27 +469,39 @@ FrameNode.prototype = {
     if (otherNode.youngestFrameSamples > 0) {
       this.youngestFrameSamples += samples;
     }
 
     if (otherNode.youngestFrameByteSize > 0) {
       this.youngestFrameByteSize += otherNode.youngestFrameByteSize;
     }
 
+    if (this._stringTable === null) {
+      this._stringTable = otherNode._stringTable;
+    }
+
     if (otherNode._optimizations) {
+      if (!this._optimizations) {
+        this._optimizations = [];
+      }
       let opts = this._optimizations;
-      if (opts === null) {
-        opts = this._optimizations = [];
-        this._stringTable = otherNode._stringTable;
-      }
       let otherOpts = otherNode._optimizations;
       for (let i = 0; i < otherOpts.length; i++) {
-        opts.push(otherOpts[i]);
+       opts.push(otherOpts[i]);
       }
     }
+
+    if (otherNode._tierData.length) {
+      let tierData = this._tierData;
+      let otherTierData = otherNode._tierData;
+      for (let i = 0; i < otherTierData.length; i++) {
+        tierData.push(otherTierData[i]);
+      }
+      tierData.sort((a, b) => a.time - b.time);
+    }
   },
 
   /**
    * Returns the parsed location and additional data describing
    * this frame. Uses cached data if possible. Takes the following
    * options:
    *
    * @param {ThreadNode} options.root
@@ -524,22 +536,19 @@ FrameNode.prototype = {
   getOptimizations: function () {
     if (!this._optimizations) {
       return null;
     }
     return new JITOptimizations(this._optimizations, this._stringTable);
   },
 
   /**
-   * Returns the optimization tiers used overtime.
+   * Returns the tiers used overtime.
    *
-   * @return {?Array<object>}
+   * @return {Array<object>}
    */
-  getOptimizationTierData: function () {
-    if (!this._tierData) {
-      return null;
-    }
+  getTierData: function () {
     return this._tierData;
   }
 };
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
--- a/browser/devtools/performance/modules/widgets/graphs.js
+++ b/browser/devtools/performance/modules/widgets/graphs.js
@@ -7,53 +7,61 @@
  * This file contains the base line graph that all Performance line graphs use.
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
 const LineGraphWidget = require("devtools/shared/widgets/LineGraphWidget");
 const BarGraphWidget = require("devtools/shared/widgets/BarGraphWidget");
+const MountainGraphWidget = require("devtools/shared/widgets/MountainGraphWidget");
 const { CanvasGraphUtils } = require("devtools/shared/widgets/Graphs");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 loader.lazyRequireGetter(this, "colorUtils",
   "devtools/css-color", true);
 loader.lazyRequireGetter(this, "getColor",
   "devtools/shared/theme", true);
 loader.lazyRequireGetter(this, "ProfilerGlobal",
   "devtools/performance/global");
 loader.lazyRequireGetter(this, "L10N",
   "devtools/performance/global", true);
 loader.lazyRequireGetter(this, "MarkersOverview",
   "devtools/performance/markers-overview", true);
+loader.lazyRequireGetter(this, "createTierGraphDataFromFrameNode",
+  "devtools/performance/jit", true);
 
 /**
  * For line graphs
  */
 const HEIGHT = 35; // px
 const STROKE_WIDTH = 1; // px
 const DAMPEN_VALUES = 0.95;
 const CLIPHEAD_LINE_COLOR = "#666";
 const SELECTION_LINE_COLOR = "#555";
-const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
-const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
-const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
+const SELECTION_BACKGROUND_COLOR_NAME = "graphs-blue";
+const FRAMERATE_GRAPH_COLOR_NAME = "graphs-green";
+const MEMORY_GRAPH_COLOR_NAME = "graphs-blue";
 
 /**
  * For timeline overview
  */
 const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
 const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
 const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
 
 /**
+ * For optimization graph
+ */
+const OPTIMIZATIONS_GRAPH_RESOLUTION = 100;
+
+/**
  * A base class for performance graphs to inherit from.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the overview.
  * @param string metric
  *        The unit of measurement for this graph.
  */
 function PerformanceGraph(parent, metric) {
@@ -81,17 +89,17 @@ PerformanceGraph.prototype = Heritage.ex
 
   /**
    * Sets the theme via `theme` to either "light" or "dark",
    * and updates the internal styling to match. Requires a redraw
    * to see the effects.
    */
   setTheme: function (theme) {
     theme = theme || "light";
-    let mainColor = getColor(this.mainColor || "highlight-blue", theme);
+    let mainColor = getColor(this.mainColor || "graphs-blue", theme);
     this.backgroundColor = getColor("body-background", theme);
     this.strokeColor = mainColor;
     this.backgroundGradientStart = colorUtils.setAlpha(mainColor, 0.2);
     this.backgroundGradientEnd = colorUtils.setAlpha(mainColor, 0.2);
     this.selectionBackgroundColor = colorUtils.setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
     this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
     this.maximumLineColor = colorUtils.setAlpha(mainColor, 0.4);
     this.averageLineColor = colorUtils.setAlpha(mainColor, 0.7);
@@ -420,12 +428,88 @@ GraphsController.prototype = {
       if (graph = yield this.isAvailable(graphName)) {
         enabled.push(graph);
       }
     }
     return this._enabledGraphs = enabled;
   }),
 };
 
+/**
+ * A base class for performance graphs to inherit from.
+ *
+ * @param nsIDOMNode parent
+ *        The parent node holding the overview.
+ * @param string metric
+ *        The unit of measurement for this graph.
+ */
+function OptimizationsGraph(parent) {
+  MountainGraphWidget.call(this, parent);
+  this.setTheme();
+}
+
+OptimizationsGraph.prototype = Heritage.extend(MountainGraphWidget.prototype, {
+
+  render: Task.async(function *(threadNode, frameNode) {
+    // Regardless if we draw or clear the graph, wait
+    // until it's ready.
+    yield this.ready();
+
+    if (!threadNode || !frameNode) {
+      this.setData([]);
+      return;
+    }
+
+    let { sampleTimes } = threadNode;
+
+    if (!sampleTimes.length) {
+      this.setData([]);
+      return;
+    }
+
+    // Take startTime/endTime from samples recorded, rather than
+    // using duration directly from threadNode, as the first sample that
+    // equals the startTime does not get recorded.
+    let startTime = sampleTimes[0];
+    let endTime = sampleTimes[sampleTimes.length - 1];
+
+    let bucketSize = (endTime - startTime) / OPTIMIZATIONS_GRAPH_RESOLUTION;
+    let data = createTierGraphDataFromFrameNode(frameNode, sampleTimes, bucketSize);
+
+    // If for some reason we don't have data (like the frameNode doesn't
+    // have optimizations, but it shouldn't be at this point if it doesn't),
+    // log an error.
+    if (!data) {
+      Cu.reportError(`FrameNode#${frameNode.location} does not have optimizations data to render.`);
+      return;
+    }
+
+    this.dataOffsetX = startTime;
+    yield this.setData(data);
+  }),
+
+  /**
+   * Sets the theme via `theme` to either "light" or "dark",
+   * and updates the internal styling to match. Requires a redraw
+   * to see the effects.
+   */
+  setTheme: function (theme) {
+    theme = theme || "light";
+
+    let interpreterColor = getColor("graphs-red", theme);
+    let baselineColor = getColor("graphs-blue", theme);
+    let ionColor = getColor("graphs-green", theme);
+
+    this.format = [
+      { color: interpreterColor },
+      { color: baselineColor },
+      { color: ionColor },
+    ];
+
+    this.backgroundColor = getColor("sidebar-background", theme);
+  }
+});
+
+exports.OptimizationsGraph = OptimizationsGraph;
 exports.FramerateGraph = FramerateGraph;
 exports.MemoryGraph = MemoryGraph;
 exports.TimelineGraph = TimelineGraph;
 exports.GraphsController = GraphsController;
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -28,26 +28,30 @@ loader.lazyRequireGetter(this, "L10N",
 loader.lazyRequireGetter(this, "PerformanceTelemetry",
   "devtools/performance/telemetry", true);
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/performance/markers", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/toolkit/performance/utils");
 loader.lazyRequireGetter(this, "GraphsController",
   "devtools/performance/graphs", true);
+loader.lazyRequireGetter(this, "OptimizationsGraph",
+  "devtools/performance/graphs", true);
 loader.lazyRequireGetter(this, "WaterfallHeader",
   "devtools/performance/waterfall-ticks", true);
 loader.lazyRequireGetter(this, "MarkerView",
   "devtools/performance/marker-view", true);
 loader.lazyRequireGetter(this, "MarkerDetails",
   "devtools/performance/marker-details", true);
 loader.lazyRequireGetter(this, "MarkerUtils",
   "devtools/performance/marker-utils");
 loader.lazyRequireGetter(this, "WaterfallUtils",
   "devtools/performance/waterfall-utils");
+loader.lazyRequireGetter(this, "FrameUtils",
+  "devtools/performance/frame-utils");
 loader.lazyRequireGetter(this, "CallView",
   "devtools/performance/tree-view", true);
 loader.lazyRequireGetter(this, "ThreadNode",
   "devtools/performance/tree-model", true);
 loader.lazyRequireGetter(this, "FrameNode",
   "devtools/performance/tree-model", true);
 loader.lazyRequireGetter(this, "JITOptimizations",
   "devtools/performance/jit", true);
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -72,17 +72,17 @@
                 class="experimental-option"
                 type="checkbox"
                 data-pref="enable-jit-optimizations"
                 label="&performanceUI.enableJITOptimizations;"
                 tooltiptext="&performanceUI.enableJITOptimizations.tooltiptext;"/>
     </menupopup>
   </popupset>
 
-  <hbox class="theme-body" flex="1">
+  <hbox id="body" class="theme-body" flex="1">
 
     <!-- Sidebar: controls and recording list -->
     <vbox id="recordings-pane">
       <toolbar id="recordings-toolbar"
                class="devtools-toolbar">
         <hbox id="recordings-controls"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="main-record-button"
@@ -299,16 +299,17 @@
                   <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
                     <hbox id="jit-optimizations-header">
                       <span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
                       <span class="header-function-name" />
                       <span class="header-file opt-url debugger-link" />
                       <span class="header-line opt-line" />
                     </hbox>
                   </toolbar>
+                  <hbox id="optimizations-graph"></hbox>
                   <vbox id="jit-optimizations-raw-view"></vbox>
                 </vbox>
               </hbox>
 
               <!-- JS FlameChart -->
               <hbox id="js-flamegraph-view" flex="1">
               </hbox>
 
@@ -359,17 +360,16 @@
                          type="function"
                          crop="end"
                          value="&performanceUI.table.function;"/>
                 </hbox>
                 <vbox class="call-tree-cells-container" flex="1"/>
               </vbox>
 
               <!-- Memory FlameChart -->
-              <hbox id="memory-flamegraph-view" flex="1">
-              </hbox>
+              <hbox id="memory-flamegraph-view" flex="1"></hbox>
             </deck>
           </deck>
         </vbox>
       </deck>
     </vbox>
   </hbox>
 </window>
--- a/browser/devtools/performance/test/unit/test_jit-graph-data.js
+++ b/browser/devtools/performance/test/unit/test_jit-graph-data.js
@@ -31,75 +31,88 @@ add_task(function test() {
 
   equal(root.samples, SAMPLE_COUNT / 2, "root has correct amount of samples");
   equal(root.sampleTimes.length, SAMPLE_COUNT / 2, "root has correct amount of sample times");
   // Add time offset since the first sample begins TIME_OFFSET after startTime
   equal(root.sampleTimes[0], startTime + TIME_OFFSET, "root recorded first sample time in scope");
   equal(root.sampleTimes[root.sampleTimes.length - 1], endTime, "root recorded last sample time in scope");
 
   let frame = getFrameNodePath(root, "X");
-  let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, { startTime, endTime, resolution: RESOLUTION });
+  let data = createTierGraphDataFromFrameNode(frame, root.sampleTimes, (endTime-startTime)/RESOLUTION);
 
   let TIME_PER_WINDOW = SAMPLE_COUNT / 2 / RESOLUTION * TIME_PER_SAMPLE;
 
-  for (let i = 0; i < 10; i++) {
-    equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x");
-    equal(data[i].ys[0], 0.2, "first window has 2 frames in interpreter");
-    equal(data[i].ys[1], 0.2, "first window has 2 frames in baseline");
-    equal(data[i].ys[2], 0.2, "first window has 2 frames in ion");
+  // Filter out the dupes created with the same delta so the graph
+  // can render correctly.
+  let filteredData = [];
+  for (let i = 0; i < data.length; i++) {
+    if (!i || data[i].delta !== data[i-1].delta) {
+      filteredData.push(data[i]);
+    }
+  }
+  data = filteredData;
+
+  for (let i = 0; i < 11; i++) {
+    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "first window has correct x");
+    equal(data[i].values[0], 0.2, "first window has 2 frames in interpreter");
+    equal(data[i].values[1], 0.2, "first window has 2 frames in baseline");
+    equal(data[i].values[2], 0.2, "first window has 2 frames in ion");
   }
-  for (let i = 10; i < 20; i++) {
-    equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x");
-    equal(data[i].ys[0], 0, "second window observed no optimizations");
-    equal(data[i].ys[1], 0, "second window observed no optimizations");
-    equal(data[i].ys[2], 0, "second window observed no optimizations");
+  // Start on 11, since i===10 is where the values change, and the new value (0,0,0)
+  // is removed in `filteredData`
+  for (let i = 11; i < 20; i++) {
+    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "second window has correct x");
+    equal(data[i].values[0], 0, "second window observed no optimizations");
+    equal(data[i].values[1], 0, "second window observed no optimizations");
+    equal(data[i].values[2], 0, "second window observed no optimizations");
   }
-  for (let i = 20; i < 30; i++) {
-    equal(data[i].x, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x");
-    equal(data[i].ys[0], 0.3, "third window has 3 frames in interpreter");
-    equal(data[i].ys[1], 0, "third window has 0 frames in baseline");
-    equal(data[i].ys[2], 0, "third window has 0 frames in ion");
+  // Start on 21, since i===20 is where the values change, and the new value (0.3,0,0)
+  // is removed in `filteredData`
+  for (let i = 21; i < 30; i++) {
+    equal(data[i].delta, startTime + TIME_OFFSET + (TIME_PER_WINDOW * i), "third window has correct x");
+    equal(data[i].values[0], 0.3, "third window has 3 frames in interpreter");
+    equal(data[i].values[1], 0, "third window has 0 frames in baseline");
+    equal(data[i].values[2], 0, "third window has 0 frames in ion");
   }
 });
 
 let gUniqueStacks = new RecordingUtils.UniqueStacks();
 
 function uniqStr(s) {
   return gUniqueStacks.getOrAddStringIndex(s);
 }
 
 const TIER_PATTERNS = [
   // 0-99
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 100-199
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 200-299
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 300-399
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 400-499
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
 
   // 500-599
-  // Test current frames in all opts, including that
-  // the same frame with no opts does not get counted
-  ["X", "X", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],
+  // Test current frames in all opts
+  ["A", "A", "A", "A", "X_1", "X_2", "X_1", "X_2", "X_0", "X_0"],
 
   // 600-699
   // Nothing for current frame
   ["A", "B", "A", "B", "A", "B", "A", "B", "A", "B"],
 
   // 700-799
   // A few frames where the frame is not the leaf node
   ["X_2 -> Y", "X_2 -> Y", "X_2 -> Y", "X_0", "X_0", "X_0", "A", "A", "A", "A"],
 
   // 800-899
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
   // 900-999
-  ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
+  ["X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0", "X_0"],
 ];
 
 function createSample (i, frames) {
   let sample = {};
   sample.time = i * TIME_PER_SAMPLE;
   sample.frames = [{ location: "(root)" }];
   if (i === 0) {
     return sample;
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -31,16 +31,17 @@ let JsCallTreeView = Heritage.extend(Det
   },
 
   /**
    * Unbinds events.
    */
   destroy: function () {
     OptimizationsListView.destroy();
     this.container = null;
+    this.threadNode = null;
     DetailsSubview.destroy.call(this);
   },
 
   /**
    * Method for handling all the set up for rendering a new call tree.
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
@@ -51,17 +52,17 @@ let JsCallTreeView = Heritage.extend(Det
     let optimizations = recording.getConfiguration().withJITOptimizations;
 
     let options = {
       contentOnly: !PerformanceController.getOption("show-platform-data"),
       invertTree: PerformanceController.getOption("invert-call-tree"),
       flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
       showOptimizationHint: optimizations
     };
-    let threadNode = this._prepareCallTree(profile, interval, options);
+    let threadNode = this.threadNode = this._prepareCallTree(profile, interval, options);
     this._populateCallTree(threadNode, options);
 
     if (optimizations) {
       this.showOptimizations();
     } else {
       this.hideOptimizations();
     }
     OptimizationsListView.reset();
@@ -74,17 +75,17 @@ let JsCallTreeView = Heritage.extend(Det
   },
 
   hideOptimizations: function () {
     $("#jit-optimizations-view").classList.add("hidden");
   },
 
   _onFocus: function (_, treeItem) {
     if (PerformanceController.getCurrentRecording().getConfiguration().withJITOptimizations) {
-      OptimizationsListView.setCurrentFrame(treeItem.frame);
+      OptimizationsListView.setCurrentFrame(this.threadNode, treeItem.frame);
       OptimizationsListView.render();
     }
 
     this.emit("focus", treeItem);
   },
 
   /**
    * Fired on the "link" event for the call tree in this container.
--- a/browser/devtools/performance/views/optimizations-list.js
+++ b/browser/devtools/performance/views/optimizations-list.js
@@ -19,66 +19,84 @@ let OptimizationsListView = {
 
   _currentFrame: null,
 
   /**
    * Initialization function called when the tool starts up.
    */
   initialize: function () {
     this.reset = this.reset.bind(this);
+    this._onThemeChanged = this._onThemeChanged.bind(this);
 
     this.el = $("#jit-optimizations-view");
     this.$headerName = $("#jit-optimizations-header .header-function-name");
     this.$headerFile = $("#jit-optimizations-header .header-file");
     this.$headerLine = $("#jit-optimizations-header .header-line");
 
     this.tree = new TreeWidget($("#jit-optimizations-raw-view"), {
       sorted: false,
       emptyText: JIT_EMPTY_TEXT
     });
+    this.graph = new OptimizationsGraph($("#optimizations-graph"));
+    this.graph.setTheme(PerformanceController.getTheme());
 
     // Start the tree by resetting.
     this.reset();
+
+    PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
   },
 
   /**
    * Destruction function called when the tool cleans up.
    */
   destroy: function () {
+    PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
     this.tree = null;
     this.$headerName = this.$headerFile = this.$headerLine = this.el = null;
   },
 
   /**
    * Takes a FrameNode, with corresponding optimization data to be displayed
    * in the view.
    *
    * @param {FrameNode} frameNode
    */
-  setCurrentFrame: function (frameNode) {
+  setCurrentFrame: function (threadNode, frameNode) {
+    if (threadNode !== this.getCurrentThread()) {
+      this._currentThread = threadNode;
+    }
     if (frameNode !== this.getCurrentFrame()) {
       this._currentFrame = frameNode;
     }
   },
 
   /**
    * Returns the current frame node for this view.
    *
    * @return {?FrameNode}
    */
-  getCurrentFrame: function (frameNode) {
+  getCurrentFrame: function () {
     return this._currentFrame;
   },
 
   /**
+   * Returns the current thread node for this view.
+   *
+   * @return {?ThreadNode}
+   */
+  getCurrentThread: function () {
+    return this._currentThread;
+  },
+
+  /**
    * Clears out data in the tree, sets to an empty state,
    * and removes current frame.
    */
   reset: function () {
-    this.setCurrentFrame(null);
+    this.setCurrentFrame(null, null);
     this.clear();
     this.el.classList.add("empty");
     this.emit(EVENTS.OPTIMIZATIONS_RESET);
     this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
   },
 
   /**
    * Clears out data in the tree.
@@ -118,20 +136,29 @@ let OptimizationsListView = {
 
     // An array of sorted OptimizationSites.
     let sites = frameNode.getOptimizations().optimizationSites;
 
     for (let site of sites) {
       this._renderSite(view, site, frameData);
     }
 
+    this._renderTierGraph();
+
     this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
   },
 
   /**
+   * Renders the optimization tier graph over time.
+   */
+  _renderTierGraph: function () {
+    this.graph.render(this.getCurrentThread(), this.getCurrentFrame());
+  },
+
+  /**
    * Creates an entry in the tree widget for an optimization site.
    */
   _renderSite: function (view, site, frameData) {
     let { id, samples, data } = site;
     let { types, attempts } = data;
     let siteNode = this._createSiteNode(frameData, site);
 
     // Cast `id` to a string so TreeWidget doesn't think it does not exist
@@ -170,17 +197,16 @@ let OptimizationsListView = {
         view.add([id, `${id}-types`, `${id}-types-${i}`, { node }]);
       }
     }
   },
 
   /**
    * Creates an element for insertion in the raw view for an OptimizationSite.
    */
-
   _createSiteNode: function (frameData, site) {
     let node = document.createElement("span");
     let desc = document.createElement("span");
     let line = document.createElement("span");
     let column = document.createElement("span");
     let urlNode = this._createDebuggerLinkNode(frameData.url, site.data.line);
 
     let attempts = site.getAttempts();
@@ -220,32 +246,30 @@ let OptimizationsListView = {
 
   /**
    * Creates an element for insertion in the raw view for an IonType.
    *
    * @see browser/devtools/performance/modules/logic/jit.js
    * @param {IonType} ionType
    * @return {Element}
    */
-
   _createIonNode: function (ionType) {
     let node = document.createElement("span");
     node.textContent = `${ionType.site} : ${ionType.mirType}`;
     node.className = "opt-ion-type";
     return node;
   },
 
   /**
    * Creates an element for insertion in the raw view for an ObservedType.
    *
    * @see browser/devtools/performance/modules/logic/jit.js
    * @param {ObservedType} type
    * @return {Element}
    */
-
   _createObservedTypeNode: function (type) {
     let node = document.createElement("span");
     let typeNode = document.createElement("span");
 
     typeNode.textContent = `${type.keyedBy}` + (type.name ? ` → ${type.name}` : "");
     typeNode.className = "opt-type";
     node.appendChild(typeNode);
 
@@ -275,17 +299,16 @@ let OptimizationsListView = {
 
   /**
    * Creates an element for insertion in the raw view for an OptimizationAttempt.
    *
    * @see browser/devtools/performance/modules/logic/jit.js
    * @param {OptimizationAttempt} attempt
    * @return {Element}
    */
-
   _createAttemptNode: function (attempt) {
     let node = document.createElement("span");
     let strategyNode = document.createElement("span");
     let outcomeNode = document.createElement("span");
 
     strategyNode.textContent = attempt.strategy;
     strategyNode.className = "opt-strategy";
     outcomeNode.textContent = attempt.outcome;
@@ -304,17 +327,16 @@ let OptimizationsListView = {
    * Can also optionally pass in an element to modify it rather than
    * creating a new one.
    *
    * @param {String} url
    * @param {Number} line
    * @param {?Element} el
    * @return {Element}
    */
-
   _createDebuggerLinkNode: function (url, line, el) {
     let node = el || document.createElement("span");
     node.className = "opt-url";
     let fileName;
 
     if (this._isLinkableURL(url)) {
       fileName = url.slice(url.lastIndexOf("/") + 1);
       node.classList.add("debugger-link");
@@ -324,17 +346,16 @@ let OptimizationsListView = {
     fileName = fileName || url || "";
     node.textContent = fileName ? `@${fileName}` : "";
     return node;
   },
 
   /**
    * Updates the headers with the current frame's data.
    */
-
   _setHeaders: function (frameData) {
     let isMeta = frameData.isMetaCategory;
     let name = isMeta ? frameData.categoryData.label : frameData.functionName;
     let url = isMeta ? "" : frameData.url;
     let line = isMeta ? "" : frameData.line;
 
     this.$headerName.textContent = name;
     this.$headerLine.textContent = line;
@@ -346,20 +367,27 @@ let OptimizationsListView = {
 
   /**
    * Takes a string and returns a boolean indicating whether or not
    * this is a valid url for linking to the debugger.
    *
    * @param {String} url
    * @return {Boolean}
    */
-
   _isLinkableURL: function (url) {
     return url && url.indexOf &&
        (url.indexOf("http") === 0 ||
         url.indexOf("resource://") === 0 ||
         url.indexOf("file://") === 0);
   },
 
+  /**
+   * Called when `devtools.theme` changes.
+   */
+  _onThemeChanged: function (_, theme) {
+    this.graph.setTheme(theme);
+    this.graph.refresh({ force: true });
+  },
+
   toString: () => "[object OptimizationsListView]"
 };
 
 EventEmitter.decorate(OptimizationsListView);
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -384,16 +384,17 @@ ResponsiveUI.prototype = {
    *    <toolbarbutton class="devtools-responsiveui-home-button" />
    *  </toolbar>
    * </vbox>
    */
   buildUI: function RUI_buildUI() {
     // Toolbar
     this.toolbar = this.chromeDoc.createElement("toolbar");
     this.toolbar.className = "devtools-responsiveui-toolbar";
+    this.toolbar.setAttribute("fullscreentoolbar", "true");
 
     this.menulist = this.chromeDoc.createElement("menulist");
     this.menulist.className = "devtools-responsiveui-menulist";
     this.menulist.setAttribute("editable", "true");
 
     this.menulist.addEventListener("select", this.bound_presetSelected, true);
     this.menulist.addEventListener("change", this.bound_handleManualInput, true);
 
--- a/browser/devtools/shared/widgets/MountainGraphWidget.js
+++ b/browser/devtools/shared/widgets/MountainGraphWidget.js
@@ -7,17 +7,17 @@ const { AbstractCanvasGraph, CanvasGraph
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 // Bar graph constants.
 
 const GRAPH_DAMPEN_VALUES_FACTOR = 0.9;
 
 const GRAPH_BACKGROUND_COLOR = "#ddd";
-const GRAPH_STROKE_WIDTH = 2; // px
+const GRAPH_STROKE_WIDTH = 1; // px
 const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
 const GRAPH_HELPER_LINES_DASH = [5]; // px
 const GRAPH_HELPER_LINES_WIDTH = 1; // px
 
 const GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
 const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
@@ -120,18 +120,18 @@ MountainGraphWidget.prototype = Heritage
       throw "The graph format traits are mandatory to style the data source.";
     }
     let { canvas, ctx } = this._getNamedCanvas("mountain-graph-data");
     let width = this._width;
     let height = this._height;
 
     let totalSections = this.format.length;
     let totalTicks = this._data.length;
-    let firstTick = this._data[0].delta;
-    let lastTick = this._data[totalTicks - 1].delta;
+    let firstTick = totalTicks ? this._data[0].delta : 0;
+    let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
 
     let duration = this.dataDuration || lastTick;
     let dataScaleX = this.dataScaleX = width / (duration - this.dataOffsetX);
     let dataScaleY = this.dataScaleY = height * this.dampenValuesFactor;
 
     // Draw the graph.
 
     let prevHeights = Array.from({ length: totalTicks }).fill(0);
--- a/browser/docs/DirectoryLinksProvider.rst
+++ b/browser/docs/DirectoryLinksProvider.rst
@@ -189,18 +189,17 @@ A suggested link has additional values:
   triggering set of sites in Firefox.
 - ``check_inadjacency`` - boolean if true prevents the suggested link from being
   shown if the new tab page is showing a site from an inadjacency list.
 - ``explanation`` - string to override the default explanation that appears
   below a Suggested Tile. %1$S is replaced by the triggering adgroup name and
   %2$S is replaced by the triggering site.
 - ``frecent_sites`` - array of strings of the sites that can trigger showing a
   Suggested Tile if the user has the site in one of the top 100 most-frecent
-  pages. Only preapproved array of strings that are hardcoded into the
-  DirectoryLinksProvider module are allowed.
+  pages.
 - ``frequency_caps`` - an object consisting of daily and total frequency caps
   that limit the number of times a Suggested Tile can be shown in the new tab
   per day and overall.
 - ``time_limits`` - an object consisting of start and end timestamps specifying
   when a Suggested Tile may start and has to stop showing in the newtab.
   The timestamp is expected in ISO_8601 format: '2014-01-10T20:00:00.000Z'
 
 The inadjacency list is packaged with Firefox as base64-encoded 1-way-hashed
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -59,188 +59,16 @@ const PREF_SELECTED_LOCALE = "general.us
 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
 
 // The preference that tells where to send click/view pings
 const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
 // The preference that tells if newtab is enhanced
 const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
-// Only allow explicitly approved frecent sites with display name
-const ALLOWED_FRECENT_SITES = new Map([
-  [ '1800petmeds.com,800petmeds.com,adopt.dogtime.com,adoptapet.com,akc.org,americanhumane.org,animal.discovery.com,animalconcerns.org,animalshelter.org,arcatapet.com,aspca.org,avma.org,bestfriends.org,blog.petmeds.com,buddydoghs.com,carealotpets.com,dailypuppy.com,dog.com,dogbar.com,dogbreedinfo.com,drsfostersmith.com,entirelypets.com,farmsanctuary.org,farmusa.org,freekibble.com,freekibblekat.com,healthypets.com,hsus.org,humanesociety.org,liveaquaria.com,marinedepot.com,medi-vet.com,nationalpetpharmacy.com,nsalamerica.org,nycacc.org,ohmydogsupplies.com,pet-dog-cat-supply-store.com,petcarerx.com,petco.com,petdiscounters.com,petedge.com,peteducation.com,petfinder.com,petfooddirect.com,petguys.com,petharbor.com,petmountain.com,petplanet.co.uk,pets911.com,petsmart.com,petsuppliesplus.com,puppyfind.com,revivalanimal.com,terrificpets.com,thatpetplace.com,theanimalrescuesite.com,theanimalrescuesite.greatergood.com,thefluffingtonpost.com,therainforestsite.com,vetdepot.com',
-    'pet' ],
-  [ '1aauto.com,autoblog.com,autoguide.com,autosite.com,autoweek.com,bimmerpost.com,bmwblog.com,boldride.com,caranddriver.com,carcomplaints.com,carspoon.com,cherokeeforum.com,classiccars.com,commercialtrucktrader.com,corvetteforum.com,dealerrater.com,ebizautos.com,ford-trucks.com,hemmings.com,jalopnik.com,jeepforum.com,jeepsunlimited.com,jk-forum.com,legendaryspeed.com,motorauthority.com,motortrend.com,motorwings.com,odometer.com,pirate4x4.com,purecars.com,roadandtrack.com,teslamotorsclub.com,topgear.com,topspeed.com,totalmini.com,truckpaper.com,wranglerforum.com',
-    'auto' ],
-  [ 'autobytel.com,autocheck.com,automotive.com,autonation.com,autos.aol.com,autos.msn.com,autos.yahoo.com,autotrader.autos.msn.com,autotrader.com,autotraderclassics.com,autoweb.com,car.com,carbuyingtips.com,carfax.com,cargurus.com,carmax.com,carprices.com,cars.com,cars.oodle.com,carsdirect.com,carsforsale.com,edmunds.com,hertzcarsales.com,imotors.com,intellichoice.com,internetautoguide.com,kbb.com,lemonfree.com,nada.com,nadaguides.com,thecarconnection.com,thetruthaboutcars.com,truecar.com,usedcars.com,usnews.rankingsandreviews.com',
-    'auto' ],
-  [ 'acura.com,audi.ca,audi.com,audiusa.com,automobiles.honda.com,bentleymotors.com,bmw.com,bmwusa.com,buick.com,buyatoyota.com,cadillac.com,cars.mclaren.com,chevrolet.com,choosenissan.com,chrysler.com,daimler.com,dodge.com,ferrari.com/en_us,fiskerautomotive.com,ford.com,gm.com,gmc.com,hummer.com,hyundai.com,hyundaiusa.com,infiniti.com,infinitiusa.com,jaguarusa.com,jeep.com,kia.com,kiamotors.com,lamborghini.com/en/home,landrover.com,landroverusa.com,lexus.com,lincoln.com,maserati.us,mazda.com,mazdausa.com,mbusa.com,mbusi.com,mercedes-amg.com,mercedes-benz.com,mercuryvehicles.com,miniusa.com,nissanusa.com,pontiac.com,porsche.com/usa,ramtrucks.com,rolls-roycemotorcars.com,saturn.com,scion.com,subaru.com,teslamotors.com,toyota.com,volkswagen.co.uk,volkswagen.com,volvocars.com/us,vw.com',
-    'auto' ],
-  [ '1010tires.com,4wheelparts.com,advanceautoparts.com,andysautosport.com,autoanything.com,autogeek.net,autopartsgiant.com,autopartswarehouse.com,autotrucktoys.com,autozone.com,autozoneinc.com,bavauto.com,bigotires.com,bilsteinus.com,brembo.com,car-part.com,carid.com,carparts.com,carquest.com,dinancars.com,discounttire.com,discounttiredirect.com,firestonecompleteautocare.com,goodyear.com,hrewheels,jcwhitney.com,kw-suspensions.com,momousa.com,napaonline.com,onlinetires.com,oreillyauto.com,oriellysautoparts.com,pepboys.com,repairpal.com,rockauto.com,shop.advanceautoparts.com,slickcar.com,stoptech.com,streetbeatcustoms.com,summitracing.com,tirebuyer.com,tirerack.com,tiresplus.com,tsw.com,velocitymotoring.com,wheelmax.com',
-    'auto parts' ],
-  [ 'abebooks.co.uk,abebooks.com,addall.com,alibris.com,allaboutcircuits.com,allbookstores.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biblio.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,goodreads.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,usedbooksearch.co.uk,valorebooks.com,valuemags.com,vialibri.net,wwnorton.com,zoobooks.com',
-    'literature' ],
-  [ '53.com,ally.com,bankofamerica.com,bbt.com,bnymellon.com,capitalone.com/bank/,chase.com,citi.com,citibank.com,citizensbank.com,citizensbankonline.com,creditonebank.com,everbank.com,hsbc.com,key.com,pnc.com,pncbank.com,rbs.co.uk,regions.com,sovereignbank.com,suntrust.com,tdbank.com,usaa.com,usbank.com,wachovia.com,wamu.com,wellsfargo.com,wsecu.org',
-    'banking' ],
-  [ '247wallst.com,bizjournals.com,bloomberg.com,businessweek.com,cnbc.com,cnnmoney.com,dowjones.com,easyhomesite.com,economist.com,entrepreneur.com,fastcompany.com,finance.yahoo.com,forbes.com,fortune.com,foxbusiness.com,ft.com,hbr.org,ibtimes.com,inc.com,manta.com,marketwatch.com,newsweek.com,online.wsj.com,qz.com,reuters.com,smartmoney.com,wsj.com',
-    'business news' ],
-  [ 'achievecard.com,americanexpress.com,barclaycardus.com,card.com,citicards.com,comparecards.com,creditcards.citi.com,discover.com,discovercard.com,experian.com,skylightpaycard.com,squareup.com,visa.com,visabuxx.com,visaextras.com',
-    'finance' ],
-  [ 'alliantcreditunion.org,connexuscu.org,lmcu.org,nasafcu.com,navyfcu.org,navyfederal.org,penfed.org,sccu.com,suncoastcreditunion.com,tinkerfcu.org,veridiancu.org',
-    'finance' ],
-  [ 'allbusiness.com,bankrate.com,buyersellertips.com,cboe.com,cnbcprime.com,coindesk.com,dailyfinance.com,dailyfx.com,dealbreaker.com,easierstreetdaily.com,economywatch.com,etfdailynews.com,etfdb.com,financeformulas.net,finviz.com,fool.com,forexpros.com,forexthreads.com,ftpress.com,fx-exchange.com,insidermonkey.com,investmentu.com,investopedia.com,investorjunkie.com,investors.com,kiplinger.com,minyanville.com,moneymorning.com,moneyning.com,moneysavingexpert.com,morningstar.com,nakedcapitalism.com,ncsoft.net,oilprice.com,realclearmarkets.com,rttnews.com,seekingalpha.com,silverdoctors.com,stockcharts.com,stockpickr.com,thefinancials.com,thestreet.com,wallstreetinsanity.com,wikinvest.com,xe.com,youngmoney.com',
-    'investing' ],
-  [ 'edwardjones.com,fidelity.com,goldmansachs.com,jpmorgan.com,ml.com,morganstanley.com,mymerrill.com,personal.vanguard.com,principal.com,schwab.com,schwabplan.com,scottrade.com,tdameritrade.com,troweprice.com,vanguard.com',
-    'investing' ],
-  [ '247lendinggroup.com,americanoneunsecured.com,avant.com,bestegg.com,chasestudentloans.com,eloan.com,gofundme.com,guidetolenders.com,kiva.org,lendacademy.com,lendingclub.com,lendingtree.com,lightstream.com,loanio.com,manageyourloans.com,meetearnest.com,microplace.com,netcredit.com,peer-lend.com,personalloans.com,prosper.com,salliemae.com,sofi.com,springleaf.com,uk.zopa.com,upstart.com',
-    'finance' ],
-  [ 'betterment.com,blooom.com,futureadvisor.com,kapitall.com,motifinvesting.com,personalcapital.com,wealthfront.com,wisebanyan.com',
-    'investing' ],
-  [ 'bancdebinary.com,cherrytrade.com,empireoption.net,etrade.com,firstrade.com,forex.com,interactivebrokers.com,ishares.com,optionsxpress.com,sharebuilder.com,thinkorswim.com,tradeking.com,trademonster.com,us.etrade.com,zecco.com',
-    'finance' ],
-  [ 'annualcreditreport.com,bluebird.com,credio.com,creditkarma.com,creditreport.com,cybersource.com,equifax.com,freecreditreport.com,freecreditscore.com,freedomdebtrelief.com,freescoreonline.com,mint.com,moneymappress.com,myfico.com,nationaldebtrelief.com,onesmartpenny.com,paypal.com,transunion.com,truecredit.com,upromise.com,vuebill.com,xpressbillpay.com,youneedabudget.com',
-    'personal finance' ],
-  [ 'angieslist.com,bloomberg.com,businessinsider.com,buydomains.com,domain.com,entrepreneur.com,fastcompany.com,forbes.com,fortune.com,godaddy.com,inc.com,manta.com,nytimes.com,openforum.com,register.com,salesforce.com,sba.gov,sbomag.com,shopsmall.americanexpress.com,smallbusiness.yahoo.com,squarespace.com,startupjournal.com,startupnation.com,weebly.com,wordpress.com,youngentrepreneur.com',
-    'business news' ],
-  [ '1040now.net,24hourtax.com,acttax.com,comparetaxsoftware.org,e-file.com,etax.com,free1040taxreturn.com,hrblock.com,intuit.com,irstaxdoctors.com,libertytax.com,octaxcol.com,pay1040.com,priortax.com,quickbooks.com,quickrefunds.com,rapidtax.com,refundschedule.com,taxact.com,taxactonline.com,taxefile.com,taxhead.com,taxhelptoday.me,taxsimple.org,turbotax.com',
-    'tax' ],
-  [ 'adeccousa.com,americasjobexchange.com,aoljobs.com,applicantpro.com,applicantstack.com,apply-4-jobs.com,apply2jobs.com,att.jobs,beyond.com,careerboutique.com,careerbuilder.com,careerflash.net,careerslocal.net,climber.com,coverlettersandresume.com,dice.com,diversityonecareers.com,employmentguide.com,everyjobforme.com,experteer.com,find.ly,findtherightjob.com,freelancer.com,gigats.com,glassdoor.com,governmentjobs.com,hrapply.com,hrdepartment.com,hrsmart.com,ihire.com,indeed.com,internships.com,itsmycareer.com,job-applications.com,job-hunt.org,job-interview-site.com,job.com,jobcentral.com,jobdiagnosis.com,jobhat.com,jobing.com,jobrapido.com,jobs.aol.com,jobs.net,jobsbucket.com,jobsflag.com,jobsgalore.com,jobsonline.com,jobsradar.com,jobster.com,jobtorch.com,jobungo.com,jobvite.com,juju.com,linkedin.com,livecareer.com,localjobster.com,mindtools.com,monster.com,myjobhelper.com,myperfectresume.com,payscale.com,pryor.com,quintcareers.com,randstad.com,recruitingcenter.net,resume-library.com,resume-now.com,roberthalf.com,salary.com,salaryexpert.com,simplyhired.com,smartrecruiters.com,snagajob.com,startwire.com,theladders.com,themuse.com,theresumator.com,thingamajob.com,usajobs.gov,ziprecruiter.com',
-    'career services' ],
-  [ 'americanheart.org,americanredcross.com,americares.org,catholiccharitiesusa.org,charitybuzz.com,charitynavigator.org,charitywater.org,directrelief.org,fao.org,habitat.org,hrw.org,imf.org,mskcc.org,ohchr.org,redcross.org,reliefweb.int,salvationarmyusa.org,savethechildren.org,un.org,undp.org,unep.org,unesco.org,unfpa.org,unhcr.org,unicef.org,unicefusa.org,unops.org,volunteermatch.org,wfp.org,who.int,worldbank.org',
-    'philanthropic' ],
-  [ 'academia.edu,albany.edu,american.edu,amity.edu,annauniv.edu,apus.edu,arizona.edu,ashford.edu,asu.edu,auburn.edu,austincc.edu,baylor.edu,bc.edu,berkeley.edu,brandeis.edu,brookings.edu,brown.edu,bu.edu,buffalo.edu,byu.edu,calpoly.edu,calstate.edu,caltech.edu,cam.ac.uk,cambridge.org,capella.edu,case.edu,clemson.edu,cmu.edu,colorado.edu,colostate-pueblo.edu,colostate.edu,columbia.edu,commnet.edu,cornell.edu,cpp.edu,csulb.edu,csun.edu,csus.edu,cuny.edu,cwru.edu,dartmouth.edu,depaul.edu,devry.edu,drexel.edu,du.edu,duke.edu,emory.edu,fau.edu,fcps.edu,fiu.edu,fordham.edu,fsu.edu,fullerton.edu,fullsail.edu,gatech.edu,gcu.edu,georgetown.edu,gmu.edu,gsu.edu,gwu.edu,harvard.edu,hawaii.edu,hbs.edu,iastate.edu,iit.edu,illinois.edu,indiana.edu,iu.edu,jhu.edu,k-state.edu,kent.edu,ku.edu,lamar.edu,liberty.edu,losrios.edu,lsu.edu,luc.edu,maine.edu,maricopa.edu,mass.edu,miami.edu,miamioh.edu,missouri.edu,mit.edu,mnscu.edu,monash.edu,msu.edu,mtu.edu,nau.edu,ncsu.edu,nd.edu,neu.edu,njit.edu,northeastern.edu,northwestern.edu,nova.edu,nyu.edu,odu.edu,ohio-state.edu,ohio.edu,okstate.edu,oregonstate.edu,osu.edu,ou.edu,ox.ac.uk,pdx.edu,pearson.com,phoenix.edu,pitt.edu,princeton.edu,psu.edu,purdue.edu,regis.edu,rice.edu,rit.edu,rochester.edu,rpi.edu,rutgers.edu,sc.edu,scu.edu,sdsu.edu,seattleu.edu,sfsu.edu,si.edu,sjsu.edu,snhu.edu,stanford.edu,stonybrook.edu,suny.edu,syr.edu,tamu.edu,temple.edu,towson.edu,ttu.edu,tufts.edu,ua.edu,uark.edu,ub.edu,uc.edu,uccs.edu,ucdavis.edu,ucf.edu,uchicago.edu,uci.edu,ucla.edu,uconn.edu,ucr.edu,ucsb.edu,ucsc.edu,ucsd.edu,ucsf.edu,udel.edu,udemy.com,ufl.edu,uga.edu,uh.edu,uic.edu,uillinois.edu,uiowa.edu,uiuc.edu,uky.edu,umass.edu,umb.edu,umbc.edu,umd.edu,umich.edu,umn.edu,umuc.edu,unc.edu,uncc.edu,unf.edu,uniminuto.edu,universityofcalifornia.edu,unl.edu,unlv.edu,unm.edu,unt.edu,uoc.edu,uoregon.edu,upc.edu,upenn.edu,upi.edu,uri.edu,usc.edu,usf.edu,usg.edu,usu.edu,uta.edu,utah.edu,utdallas.edu,utexas.edu,utk.edu,uvm.edu,uw.edu,uwm.edu,vanderbilt.edu,vccs.edu,vcu.edu,virginia.edu,vt.edu,waldenu.edu,washington.edu,wayne.edu,wednet.edu,wgu.edu,wisc.edu,wisconsin.edu,wm.edu,wmich.edu,wsu.edu,wustl.edu,wvu.edu,yale.edu',
-    'college' ],
-  [ 'collegeboard.com,collegeconfidential.com,collegeview.com,ecollege.com,finaid.org,find-colleges-now.com,ratemyprofessors.com,ratemyteachers.com,studentsreview.com',
-    'college' ],
-  [ 'actstudent.org,adaptedmind.com,aesoponline.com,archives.com,bibme.org,blackboard.com,bookrags.com,cengage.com,chegg.com,classdojo.com,classzone.com,cliffsnotes.com,coursecompass.com,educationconnection.com,educationdynamics.com,ets.org,familysearch.org,fastweb.com,genealogy.com,gradesaver.com,instructure.com,khanacademy.org,learn4good.com,mathway.com,mathxl.com,mcgraw-hill.com,merriam-webster.com,mheducation.com,niche.com,openstudy.com,pearsoned.com,pearsonmylabandmastering.com,pearsonsuccessnet.com,poptropica.com,powerschool.com,proprofs.com,purplemath.com,quizlet.com,readwritethink.org,renlearn.com,rhymezone.com,schoolloop.com,schoology.com,smithsonianmag.com,sparknotes.com,study.com,studyisland.com,studymode.com,synonym.com,teacherprobs.com,teacherspayteachers.com,tutorvista.com,vocabulary.com,yourschoolmatch.com',
-    'education' ],
-  [ 'browardschools.com,k12.ca.us,k12.fl.us,k12.ga.us,k12.in.us,k12.mn.us,k12.mo.us,k12.nc.us,k12.nj.us,k12.oh.us,k12.va.us,k12.wi.us',
-    'education' ],
-  [ 'coolmath-games.com,coolmath.com,coolmath4kids.com,coolquiz.com,funbrain.com,funtrivia.com,gamesforthebrain.com,girlsgogames.com,hoodamath.com,lumosity.com,math.com,mathsisfun.com,trivia.com,wizard101.com',
-    'learning games' ],
-  [ 'askmen.com,boredomtherapy.com,buzzfeed.com,complex.com,dailymotion.com,elitedaily.com,gawker.com,howstuffworks.com,instagram.com,madamenoire.com,polygon.com,ranker.com,rollingstone.com,ted.com,theblaze.com,thechive.com,thecrux.com,thedailybeast.com,thoughtcatalog.com,uproxx.com,upworthy.com,zergnet.com',
-    'entertainment' ],
-  [ '11points.com,7gid.com,adultswim.com,break.com,cheezburger.com,collegehumor.com,cracked.com,dailydawdle.com,damnlol.com,dumb.com,dumblaws.com,ebaumsworld.com,explosm.net,failblog.org,fun-gallery.com,funnygig.com,funnyjunk.com,funnymama.com,funnyordie.com,funnytear.com,funplus.com,glassgiant.com,goingviralposts.com,gorillamask.net,i-am-bored.com,icanhascheezburger.com,ifunny.com,imjussayin.co,inherentlyfunny.com,izismile.com,jokes.com,keenspot.com,knowyourmeme.com,laughstub.com,memebase.com,mememaker.net,metacafe.com,mylol.com,picslist.com,punoftheday.com,queendom.com,rajnikantvscidjokes.in,regretfulmorning.com,shareonfb.com,somethingawful.com,stupidvideos.com,superfunnyimages.com,thedailywh.at,theonion.com,tosh.comedycentral.com,uberhumor.com,welltimedphotos.com',
-    'humor' ],
-  [ 'air.tv,amctheatres.com,boxofficemojo.com,cinapalace.com,cinaplay.com,cinemablend.com,cinemark.com,cinematical.com,collider.com,comicbookmovie.com,comingsoon.net,crackle.com,denofgeek.us,dreamworks.com,empireonline.com,enstarz.com,fandango.com,filmschoolrejects.com,flickeringmyth.com,flixster.com,fullmovie2k.com,g2g.fm,galleryhip.com,hollywood.com,hollywoodreporter.com,iglomovies.com,imdb.com,indiewire.com,instantwatcher.com,joblo.com,kickass.to,kissdrama.net,marcustheatres.com,megashare9.com,moviefone.com,movieinsider.com,moviemistakes.com,moviepilot.com,movierulz.com,movies.com,movies.yahoo.com,movieseum.com,movietickets.com,movieweb.com,mrmovietimes.com,mymovieshub.com,netflix.com,onlinemovies.pro,pelis24.com,projectfreetv.ch,redbox.com,regmovies.com,repelis.tv,rogerebert.suntimes.com,ropeofsilicon.com,rottentomatoes.com,sidereel.com,slashfilm.com,solarmovie.is,starwars.com,superherohype.com,tcm.com,twomovies.us,variety.com,vimeo.com,viooz.ac,warnerbros.com,watchfree.to,wbredirect.com,youtubeonfire.com,zmovie.tw,zumvo.com',
-    'movie' ],
-  [ '1079ishot.com,2dopeboyz.com,8tracks.com,acdc.com,allaccess.com,allhiphop.com,allmusic.com,audiofanzine.com,audiomack.com,azlyrics.com,baeblemusic.com,bandsintown.com,billboard.com,brooklynvegan.com,brunomars.com,buzznet.com,cmt.com,coachella.com,consequenceofsound.net,contactmusic.com,countryweekly.com,dangerousminds.net,datpiff.com,ddotomen.com,diffuser.fm,directlyrics.com,djbooth.net,eventful.com,fireflyfestival.com,genius.com,guitartricks.com,harmony-central.com,hiphopdx.com,hiphopearly.com,hypem.com,idolator.com,iheart.com,jambase.com,kanyetothe.com,knue.com,lamusica.com,last.fm,livemixtapes.com,loudwire.com,lyricinterpretations.com,lyrics.net,lyricsbox.com,lyricsmania.com,lyricsmode.com,metal-archives.com,metrolyrics.com,mp3.com,mtv.co.uk,myspace.com,newnownext.com,noisetrade.com,okayplayer.com,pandora.com,phish.com,pigeonsandplanes.com,pitchfork.com,popcrush.com,radio.com,rap-up.com,rdio.com,reverbnation.com,revolvermag.com,rockhall.com,saavn.com,songlyrics.com,soundcloud.com,spin.com,spinrilla.com,spotify.com,stereogum.com,stereotude.com,talkbass.com,tasteofcountry.com,thebacklot.com,theboombox.com,theboot.com,thissongissick.com,tunesbaby.com,ultimate-guitar.com,ultimateclassicrock.com,vevo.com,vibe.com,vladtv.com,whosampled.com,wikibit.me,worldstarhiphop.com,wyrk.com,xxlmag.com',
-    'music' ],
-  [ 'aceshowbiz.com,aintitcoolnews.com,allkpop.com,askkissy.com,atraf.co.il,audioboom.com,beamly.com,beyondhollywood.com,blastr.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,buzzsugar.com,cambio.com,celebdirtylaundry.com,celebrity-gossip.net,celebuzz.com,chisms.net,comicsalliance.com,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,dlisted.com,egotastic.com,empirenews.net,enelbrasero.com,eonline.com,etonline.com,ew.com,extratv.com,facade.com,famousfix.com,fanaru.com,fanpop.com,fansshare.com,fhm.com,geektyrant.com,glamourpage.com,gossipcenter.com,gossipcop.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodlife.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.buzznet.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,laineygossip.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,nationalenquirer.com,necolebitchie.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,okmagazine.com,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popsugar.com,purepeople.com,radaronline.com,rantchic.com,realitytea.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screencrush.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,socialitelife.com,sourcefednews.com,splitsider.com,starcasm.net,starmagazine.com,starpulse.com,straightfromthea.com,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,thehollywoodgossip.com,theresacaputo.com,thesuperficial.com,thezooom.com,tmz.com,tvnotas.com.mx,twanatells.com,usmagazine.com,vanityfair.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com',
-    'entertainment news' ],
-  [ 'abc.go.com,abcfamily.go.com,abclocal.go.com,accesshollywood.com,aetv.com,amctv.com,animalplanet.com,bbcamerica.com,bet.com,biography.com,bravotv.com,cartoonnetwork.com,cbn.com,cbs.com,cc.com,centrictv.com,cinemax.com,comedycentral.com,ctv.ca,cwtv.com,daytondailynews.com,drphil.com,dsc.discovery.com,fox.com,fox23.com,fox4news.com,fxnetworks.com,hbo.com,history.com,hulu.com,ifc.com,iqiyi.com,jeopardy.com,kfor.com,logotv.com,mtv.com,myfoxchicago.com,myfoxdc.com,myfoxmemphis.com,myfoxphilly.com,nbc.com,nbcchicago.com,oxygen.com,pbs.org,pbskids.org,rachaelrayshow.com,rtve.es,scifi.com,sho.com,showtimeanytime.com,spike.com,sundance.tv,syfy.com,tbs.com,teamcoco.com,telemundo.com,thedoctorstv.com,titantv.com,tlc.com,tlc.discovery.com,tnt.tv,tntdrama.com,tv.com,tvguide.com,tvseriesfinale.com,usanetwork.com,uvidi.com,vh1.com,viki.com,watchcartoononline.com,watchseries-online.ch,wetv.com,wheeloffortune.com,whio.com,wnep.com,wral.com,wtvr.com,xfinitytv.com,yidio.com',
-    'TV show' ],
-  [ 'americanhiking.org,appalachiantrail.org,canadiangeographic.ca,defenders.org,discovermagazine.com,discoveroutdoors.com,dsc.discovery.com,earthtouchnews.com,edf.org,epa.gov,ewg.org,fishngame.org,foe.org,fs.fed.us,geography.about.com,landtrustalliance.org,nationalgeographic.com,nature.com,nrdc.org,nwf.org,outdoorchannel.com,outdoors.org,seedmagazine.com,trcp.org,usda.gov,worldwildlife.org',
-    'environment' ],
-  [ 'abbreviations.com,abcmouse.com,abcya.com,achieve3000.com,ancestry.com,animaljam.com,babble.com,babycenter.com,babynamespedia.com,behindthename.com,bestmomstv.com,brainyquote.com,cafemom.com,citationmachine.net,clubpenguin.com,cutemunchkins.com,discoveryeducation.com,disney.com,easybib.com,education.com,enotes.com,everydayfamily.com,familyeducation.com,gamefaqs.com,greatschools.org,hrw.com,imvu.com,infoplease.com,itsybitsysteps.com,justmommies.com,k12.com,kidsactivitiesblog.com,mathwarehouse.com,mom.me,mom365.com,mommyshorts.com,momswhothink.com,momtastic.com,monsterhigh.com,myheritage.com,nameberry.com,nickmom.com,pampers.com,parenthood.com,parenting.com,parenting.ivillage.com,parents.com,parentsociety.com,raz-kids.com,regentsprep.org,scarymommy.com,scholastic.com,shmoop.com,softschools.com,spanishdict.com,starfall.com,thebump.com,thefreedictionary.com,thenest.com,thinkbabynames.com,todaysparent.com,webkinz.com,whattoexpect.com',
-    'family' ],
-  [ 'americangirl.com,barbie.com,barbiecollectibles.com,cartoonnetworkshop.com,chuckecheese.com,coloring.ws,disney.co.uk,disney.com.au,disney.go.com,disney.in,disneychannel-asia.com,disneyinternational.com,disneyjunior.com,disneylatino.com,disneyme.com,dltk-kids.com,dressupone.com,fantage.com,funbrainjr.com,hotwheels.com,icarly.com,kiwicrate.com,marvel.com,marvelkids.com,mattelgames.com,maxsteel.com,monkeyquest.com,nick-asia.com,nick.co.uk,nick.com,nick.tv,nickelodeon.com.au,nickjr.co.uk,nickjr.com,ninjakiwi.com,notdoppler.com,powerrangers.com,sciencekids.co.nz,search.disney.com,seventeen.com,teennick.com,theslap.com,yepi.com',
-    'family' ],
-  [ 'alabama.gov,archives.gov,bls.gov,ca.gov,cancer.gov,cdc.gov,census.gov,cia.gov,cms.gov,commerce.gov,ct.gov,delaware.gov,dhs.gov,doi.gov,dol.gov,dot.gov,ed.gov,eftps.gov,epa.gov,fbi.gov,fda.gov,fema.gov,flhsmv.gov,ftc.gov,ga.gov,georgia.gov,gpo.gov,hhs.gov,house.gov,hud.gov,illinois.gov,in.gov,irs.gov,justice.gov,ky.gov,loc.gov,louisiana.gov,maryland.gov,mass.gov,michigan.gov,mo.gov,nih.gov,nj.gov,nps.gov,ny.gov,nyc.gov,ohio.gov,ok.gov,opm.gov,oregon.gov,pa.gov,recreation.gov,sba.gov,sc.gov,sec.gov,senate.gov,state.fl.us,state.gov,state.il.us,state.ma.us,state.mi.us,state.mn.us,state.nc.us,state.ny.us,state.oh.us,state.pa.us,studentloans.gov,telldc.com,texas.gov,tn.gov,travel.state.gov,tsa.gov,usa.gov,uscis.gov,uscourts.gov,usda.gov,usdoj.gov,usembassy.gov,usgs.gov,utah.gov,va.gov,virginia.gov,wa.gov,whitehouse.gov,wi.gov,wisconsin.gov',
-    'government' ],
-  [ 'beachbody.com,bodybuilding.com,caloriecount.com,extremefitness.com,fitbit.com,fitday.com,fitnessmagazine.com,fitnessonline.com,fitwatch.com,livestrong.com,maxworkouts.com,mensfitness.com,menshealth.com,muscleandfitness.com,muscleandfitnesshers.com,myfitnesspal.com,shape.com,womenshealthmag.com',
-    'health & fitness' ],
-  [ 'activebeat.com,alliancehealth.com,beyonddiet.com,caring.com,complete-health-and-happiness.com,diabeticconnect.com,doctoroz.com,everydayhealth.com,followmyhealth.com,greatist.com,health.com,healthboards.com,healthcaresource.com,healthgrades.com,healthguru.com,healthination.com,healthtap.com,helpguide.org,iherb.com,kidshealth.org,lifescript.com,lovelivehealth.com,medicaldaily.com,mercola.com,perfectorigins.com,prevention.com,qualityhealth.com,questdiagnostics.com,realself.com,sharecare.com,sparkpeople.com,spryliving.com,steadyhealth.com,symptomfind.com,ucomparehealthcare.com,vitals.com,webmd.com,weightwatchers.com,wellness.com,zocdoc.com',
-    'health & wellness' ],
-  [ 'aetna.com,anthem.com,athenahealth.com,bcbs.com,bluecrossca.com,cigna.benefitnation.net,cigna.com,cigna.healthplan.com,ehealthcare.com,ehealthinsurance.com,empireblue.com,goldenrule.com,healthcare.gov,healthnet.com,humana-medicare.com,humana.com,kaiserpermanente.org,metlife.com,my.cigna.com,mybenefits.metlife.com,myuhc.com,uhc.com,unitedhealthcareonline.com,walterrayholt.com',
-    'health insurance' ],
-  [ 'aafp.org,americanheart.org,apa.org,cancer.org,cancercenter.com,caremark.com,clevelandclinic.org,diabetesfree.org,drugs.com,emedicinehealth.com,express-scripts.com,familydoctor.org,goodrx.com,healthcaremagic.com,healthfinder.gov,healthline.com,ieee.org,intelihealth.com,labcorp.com,livecellresearch.com,mayoclinic.com,mayoclinic.org,md.com,medcohealth.com,medhelp.org,medicalnewstoday.com,medicare.gov,medicaresupplement.com,medicinenet.com,medscape.com,memorialhermann.org,merckmanuals.com,patient.co.uk,psychcentral.com,psychology.org,psychologytoday.com,rightdiagnosis.com,rxlist.com,socialpsychology.org,spine-health.com,who.int',
-    'health & wellness' ],
-  [ 'aaa.com,aig.com,allianz-assistance.com,allstate.com,allstateagencies.com,amfam.com,amica.com,autoquotesdirect.com,esurance.com,farmers.com,farmersagent.com,geico.com,general-car-insurance-quotes.net,insurance.com,libertymutual.com,libertymutualgroup.com,mercuryinsurance.com,nationwide.com,progressive.com,progressiveagent.com,progressiveinsurance.com,provide-insurance.com,safeco.com,statefarm.com,thehartford.com,travelers.com,usaa.com',
-    'insurance' ],
-  [ '101cookbooks.com,allrecipes.com,bettycrocker.com,bonappetit.com,chocolateandzucchini.com,chow.com,chowhound.chow.com,cookinglight.com,cooks.com,cooksillustrated.com,cooksrecipes.com,delish.com,eater.com,eatingwell.com,epicurious.com,food.com,foodandwine.com,foodgawker.com,foodnetwork.com,gourmet.com,grouprecipes.com,homemaderecipes.co,iheartnaptime.net,kraftfoods.com,kraftrecipes.com,myrecipes.com,opentable.com,pillsbury.com,recipe.com,recipesource.com,recipezaar.com,saveur.com,seriouseats.com,simplyrecipes.com,smittenkitchen.com,southernliving.com,supercook.com,tasteofhome.com,tastespotting.com,technicpack.net,thekitchn.com,urbanspoon.com,wonderhowto.com,yelp.com,yummly.com,zagat.com',
-    'food & lifestyle' ],
-  [ 'aarp.org,allure.com,bustle.com,cosmopolitan.com,diply.com,eharmony.com,elle.com,glamour.com,grandascent.com,harpersbazaar.com,hellogiggles.com,instructables.com,instyle.com,marieclaire.com,match.com,mindbodygreen.com,nymag.com,okcupid.com,petco.com,photobucket.com,pof.com,rantlifestyle.com,redbookmag.com,reddit.com,sheknows.com,style.com,stylebistro.com,theilovedogssite.com,theknot.com,thescene.com,thrillist.com,vogue.com,womansday.com,youngcons.com,yourdictionary.com',
-    'lifestyle' ],
-  [ 'apartmentratings.com,apartmenttherapy.com,architectmagazine.com,architecturaldigest.com,askthebuilder.com,bhg.com,bobvila.com,countryhome.com,countryliving.com,davesgarden.com,decor8blog.com,decorpad.com,diycozyhome.com,diyideas.com,diynetwork.com,doityourself.com,domainehome.com,dwell.com,elledecor.com,familyhandyman.com,frontdoor.com,gardenguides.com,gardenweb.com,getdecorating.com,goodhousekeeping.com,hgtv.com,hgtvgardens.com,hobbylobby.com,homeadvisor.com,homerepair.about.com,hometalk.com,hometime.com,hometips.com,housebeautiful.com,houzz.com,inhabitat.com,lonny.com,makingitlovely.com,marthastewart.com,michaels.com,myhomeideas.com,realsimple.com,remodelista.com,shanty-2-chic.com,styleathome.com,thehandmadehome.net,thehealthyhomeeconomist.com,thisoldhouse.com,traditionalhome.com,trulia.com,younghouselove.com',
-    'home & lifestyle' ],
-  [ '10best.com,10tv.com,11alive.com,19actionnews.com,9news.com,abcnews.com,abcnews.go.com,adweek.com,ajc.com,anchorfree.us,arcamax.com,austin360.com,azcentral.com,bbc.co.uk,boston.com,bostonglobe.com,capecodonline.com,cbsnews.com,cheatsheet.com,chicagotribune.com,chron.com,citylab.com,cnn.com,csmonitor.com,dailyitem.com,dailymail.co.uk,dallasnews.com,eleconomista.es,examiner.com,fastcolabs.com,fivethirtyeight.com,foursquare.com,foxcarolina.com,foxnews.com,globalnews.ca,greatergood.com,guardian.co.uk,historynet.com,huffingtonpost.co.uk,huffingtonpost.com,ijreview.com,independent.co.uk,journal-news.com,kare11.com,kcra.com,kctv5.com,kgw.com,khou.com,king5.com,kirotv.com,kitv.com,kmbc.com,knoxnews.com,kpho.com,kptv.com,kron4.com,ksdk.com,ksl.com,ktvb.com,kvue.com,kxan.com,latimes.com,lifehack.org,littlethings.com,mailtribune.com,mic.com,mirror.co.uk,msn.com,msnbc.com,msnbc.msn.com,myfoxboston.com,nbcnews.com,nbcnewyork.com,newburyportnews.com,news.bbc.co.uk,news.yahoo.com,news12.com,newschannel10.com,newsday.com,newser.com,newsmax.com,newyorker.com,nj.com,nj1015.com,npr.org,nydailynews.com,nypost.com,nytimes.com,palmbeachpost.com,patch.com,philly.com,phys.org,poconorecord.com,prnewswire.com,rare.us,realclearworld.com,record-eagle.com,richmond.com,rt.com,salemnews.com,salon.com,sfgate.com,slate.com,statesman.com,suntimes.com,takepart.com,telegraph.co.uk,theatlantic.com,thedailystar.com,theguardian.com,theroot.com,theverge.com,time.com,timesonline.co.uk,topix.com,usatoday.com,usatoday30.usatoday.com,usnews.com,vice.com,vox.com,wane.com,washingtonpost.com,washingtontimes.com,wave3.com,wavy.com,wbaltv.com,wbir.com,wcnc.com,wdbj7.com,westernjournalism.com,wfaa.com,wfsb.com,wftv.com,wgal.com,wishtv.com,wisn.com,wistv.com,wivb.com,wkyc.com,wlwt.com,wmur.com,woodtv.com,wpxi.com,wsbtv.com,wsfa.com,wsmv.com,wsoctv.com,wthr.com,wtnh.com,wtsp.com,wwltv.com,wyff4.com,wzzm13.com',
-    'news' ],
-  [ 'aei.org,breitbart.com,conservativetalknow.com,conservativetribune.com,dailykos.com,ddo.com,drudgereport.com,dscc.org,foreignpolicy.com,franklinprosperityreport.com,freedomworks.org,macleans.ca,mediamatters.org,militarytimes.com,nationaljournal.com,nationalreview.com,politicalwire.com,politico.com,pressrepublican.com,qpolitical.com,realclearpolitics.com,talkingpointsmemo.com,thehill.com,thenation.com,thinkprogress.org,tnr.com,worldoftanks.eu',
-    'news' ],
-  [ 'americanscientist.org,discovermagazine.com,iflscience.com,livescience.com,nasa.gov,nationalgeographic.com,nature.com,newscientist.com,popsci.com,sciencedaily.com,sciencemag.org,sciencenews.org,scientificamerican.com,space.com,zmescience.com',
-    'science' ],
-  [ 'accuweather.com,intellicast.com,noaa.gov,ssa.gov,theweathernetwork.com,weather.com,weather.gov,weather.yahoo.com,weatherbug.com,weatherunderground.com,weatherzone.com.au,wunderground.com,www.weather.com',
-    'weather' ],
-  [ 'bhphotovideo.com,bigfolio.com,bigstockphoto.com,cameralabs.com,canonrumors.com,canstockphoto.com,digitalcamerareview.com,dpreview.com,expertphotography.com,gettyimages.com,icp.org,imaging-resource.com,intothedarkroom.com,istockphoto.com,nikonusa.com,photos.com,shutterstock.com,slrgear.com,the-digital-picture.com,thephotoargus.com,usa.canon.com,whatdigitalcamera.com,zenfolio.com',
-    'photography' ],
-  [ 'abercrombie.com,ae.com,aeropostale.com,anthropologie.com,bananarepublic.com,buycostumes.com,chadwicks.com,express.com,forever21.com,freepeople.com,hm.com,hollisterco.com,jcrew.com,jessicalondon.com,kingsizemen.com,lordandtaylor.com,lulus.com,metrostyle.com,nomorerack.com,oldnavy.com,oldnavy.gap.com,polyvore.com,rackroomshoes.com,ralphlauren.com,refinery29.com,roamans.com,sammydress.com,shop.nordstrom.com,shopbop.com,topshop.com,urbanoutfitters.com,victoriassecret.com,wetseal.com,womanwithin.com',
-    'shopping' ],
-  [ 'bizrate.com,compare99.com,coupons.com,dealtime.com,epinions.com,junglee.com,kijiji.ca,pricegrabber.com,pronto.com,redplum.com,retailmenot.com,shopping.com,shopzilla.com,smarter.com,valpak.com',
-    'shopping' ],
-  [ '123greetings.com,1800baskets.com,1800flowers.com,americangreetings.com,birthdayexpress.com,bluemountain.com,e-cards.com,egreetings.com,florists.com,ftd.com,gifts.com,groupcard.com,harryanddavid.com,hipstercards.com,kabloom.com,personalcreations.com,proflowers.com,redenvelope.com,someecards.com',
-    'flowers & gifts' ],
-  [ '6pm.com,alibaba.com,aliexpress.com,amazon.co.uk,amazon.com,asos.com,bathandbodyworks.com,bloomingdales.com,bradsdeals.com,buy.com,cafepress.com,circuitcity.com,clarkhoward.com,consumeraffairs.com,costco.com,cvs.com,dhgate.com,diapers.com,dillards.com,ebates.com,ebay.com,ebaystores.com,etsy.com,fingerhut.com,groupon.com,hsn.com,jcpenney.com,kmart.com,kohls.com,kroger.com,lowes.com,macys.com,menards.com,nextag.com,nordstrom.com,orientaltrading.com,overstock.com,qvc.com,racked.com,rewardsnetwork.com,samsclub.com,sears.com,sephora.com,shopathome.com,shopify.com,shopstyle.com,slickdeals.net,soap.com,staples.com,target.com,toptenreviews.com,vistaprint.com,walgreens.com,walmart.ca,walmart.com,wayfair.com,zappos.com,zazzle.com,zulily.com',
-    'shopping' ],
-  [ 'acehardware.com,ashleyfurniture.com,bedbathandbeyond.com,brylanehome.com,casa.com,cb2.com,crateandbarrel.com,dwr.com,ethanallen.com,furniture.com,harborfreight.com,hayneedle.com,homedecorators.com,homedepot.com,ikea.com,info.ikea-usa.com,landofnod.com,pier1.com,plowhearth.com,potterybarn.com,restorationhardware.com,roomandboard.com,westelm.com,williams-sonoma.com',
-    'home shopping' ],
-  [ 'alexandermcqueen.com,bergdorfgoodman.com,bottegaveneta.com,burberry-bluelabel.com,burberry.com,chanel.com,christianlouboutin.com,coach.com,diesel.com,dior.com,dolcegabbana.com,dolcegabbana.it,fendi.com,ferragamo.com,giorgioarmani.com,givenchy.com,gucci.com,guess.com,hermes.com,jeanpaulgaultier.com,jimmychoo.com,juicycouture.com,katespade.com,louisvuitton.com,manoloblahnik.com,marcjacobs.com,neimanmarcus.com,net-a-porter.com,paulsmith.co.uk,prada.com,robertocavalli.com,saksfifthavenue.com,toryburch.com,valentino.com,versace.com,vuitton.com,ysl.com,yslbeautyus.com',
-    'luxury shopping' ],
-  [ 'bargainseatsonline.com,livenation.com,stubhub.com,ticketfly.com,ticketliquidator.com,ticketmaster.com,tickets.com,ticketsnow.com,ticketweb.com,vividseats.com',
-    'events & tickets' ],
-  [ 'babiesrus.com,brothers-brick.com,etoys.com,fao.com,fisher-price.com,hasbro.com,hasbrotoyshop.com,lego.com,legoland.com,mattel.com,toys.com,toysrus.com,toystogrowon.com,toywiz.com',
-    'toys & games' ],
-  [ 'challengegames.nfl.com,fantasy.nfl.com,fantasyfootballblog.net,fantasyfootballcafe.com,fantasyfootballnerd.com,fantasysmarts.com,fftoday.com,fftoolbox.com,football.fantasysports.yahoo.com,footballsfuture.com,mrfootball.com,officefootballpool.com,thehuddle.com',
-    'fantasy football' ],
-  [ 'dailyjoust.com,draftday.com,draftking.com,draftkings.com,draftstreet.com,fanduel.com,realmoneyfantasyleagues.com,thedailyaudible.com',
-    'fantasy sports' ],
-  [ 'cdmsports.com,fanball.com,fantasyguru.com,fantasynews.cbssports.com,fantasyquestions.com,fantasyrundown.com,fantasysharks.com,fantasysports.yahoo.com,fantazzle.com,fantrax.com,fleaflicker.com,junkyardjake.com,kffl.com,mockdraftcentral.com,myfantasyleague.com,rototimes.com,rotowire.com,rotoworld.com,rtsports.com,whatifsports.com',
-    'fantasy sports' ],
-  [ 'football.about.com,football.com,footballoutsiders.com,nationalfootballpost.com,nflalumni.org,nflpa.com,nfltraderumors.co,profootballhof.com,profootballtalk.com,profootballtalk.nbcsports.com,profootballweekly.com',
-    'football' ],
-  [ '49ers.com,atlantafalcons.com,azcardinals.com,baltimoreravens.com,bengals.com,buccaneers.com,buffalobills.com,chargers.com,chicagobears.com,clevelandbrowns.com,colts.com,dallascowboys.com,denverbroncos.com,detroitlions.com,giants.com,houstontexans.com,jaguars.com,kcchiefs.com,miamidolphins.com,neworleanssaints.com,newyorkjets.com,packers.com,panthers.com,patriots.com,philadelphiaeagles.com,raiders.com,redskins.com,seahawks.com,steelers.com,stlouisrams.com,titansonline.com,vikings.com',
-    'football' ],
-  [ 'baseball-reference.com,baseballamerica.com,europeantour.com,golf.com,golfdigest.com,lpga.com,milb.com,minorleagueball.com,mlb.com,mlb.mlb.com,nascar.com,nba.com,ncaa.com,nhl.com,pga.com,pgatour.com,prowrestling.com,surfermag.com,surfline.com,surfshot.com,thehockeynews.com,tsn.com,ufc.com,worldgolfchampionships.com,wwe.com',
-    'sports' ],
-  [ '247sports.com,active.com,armslist.com,basketball-reference.com,bigten.org,bleacherreport.com,bleedinggreennation.com,bloodyelbow.com,cagesideseats.com,cbssports.com,cinesport.com,collegespun.com,cricbuzz.com,crictime.com,csnphilly.com,csnwashington.com,cstv.com,eastbay.com,espn.com,espn.go.com,espncricinfo.com,espnfc.com,espnfc.us,espnradio.com,eteamz.com,fanatics.com,fansided.com,fbschedules.com,fieldandstream.com,flightclub.com,foxsports.com,givemesport.com,goduke.com,goheels.com,golfchannel.com,golfnow.com,grantland.com,grindtv.com,hoopshype.com,icc-cricket.com,imleagues.com,kentuckysportsradio.com,larrybrownsports.com,leaguelineup.com,maxpreps.com,mlbtraderumors.com,mmafighting.com,mmajunkie.com,mmamania.com,msn.foxsports.com,myscore.com,nbcsports.com,nbcsports.msnbc.com,nesn.com,rantsports.com,realclearsports.com,reserveamerica.com,rivals.com,runnersworld.com,sbnation.com,scout.com,sherdog.com,si.com,speedsociety.com,sportingnews.com,sports.yahoo.com,sportsillustrated.cnn.com,sportsmanias.com,sportsmonster.us,sportsonearth.com,stack.com,teamworkonline.com,thebiglead.com,thescore.com,trails.com,triblive.com,upickem.net,usatodayhss.com,watchcric.net,yardbarker.com',
-    'sports news' ],
-  [ 'adidas.com,backcountry.com,backcountrygear.com,cabelas.com,champssports.com,competitivecyclist.com,dickssportinggoods.com,finishline.com,footlocker.com,ladyfootlocker.com,modells.com,motosport.com,mountaingear.com,newbalance.com,nike.com,patagonia.com,puma.com,reebok.com,sportsmansguide.com,steepandcheap.com,tgw.com,thenorthface.com',
-    'sports & outdoor goods' ],
-  [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org',
-    'technology' ],
-  [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com',
-    'technology' ],
-  [ 'adobe.com,asus.com,avast.com,data.com,formstack.com,gboxapp.com,gotomeeting.com,hp.com,htc.com,ibm.com,intel.com,java.com,logme.in,mcafee.com,mcafeesecure.com,microsoftstore.com,norton.com,office.com,office365.com,opera.com,oracle.com,proboards.com,samsung.com,sourceforge.net,squarespace.com,techtarget.com,ultipro.com,uniblue.com,web.com,winzip.com',
-    'technology' ],
-  [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,afterdawn.com,akihabaranews.com,appsrumors.com,avg.com,belkin.com,besttechinfo.com,betanews.com,botcrawl.com,breakingmuscle.com,cheap-phones.com,chip.de,chip.eu,citeworld.com,cleanpcremove.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,csoonline.com,daniweb.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dslreports.com,edugeek.net,eetimes.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,gizmodo.co.uk,globalsecurity.org,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,it168.com,itechpost.com,itpro.co.uk,jailbreaknation.com,laptoping.com,lightreading.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pchome.net,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,programming4.us,quickpwn.com,redmondpie.com,redorbit.com,safer-networking.org,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,sparkfun.com,speedguide.net,stuff.tv,symantec.com,taplikahome.com,techdailynews.net,techeblog.com,techie-buzz.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techradar.com,techshout.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,thewindowsclub.com,tomsitpro.com,trustedreviews.com,tuaw.com,tweaktown.com,unwiredview.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wp-themes.com,xml.com,zol.com.cn',
-    'technology' ],
-  [ 'addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org',
-    'Mozilla' ],
-  [ 'addictivetips.com,allthingsd.com,anandtech.com,androidcentral.com,androidpolice.com,arstechnica.com,bgr.com,boygeniusreport.com,cio.com,cnet.com,computerworld.com,crn.com,electronista.com,engadget.com,extremetech.com,fastcocreate.com,fastcodesign.com,fastcoexist.com,frontlinek12.com,gigaom.com,gizmag.com,gizmodo.com,greenbot.com,howtogeek.com,idigitaltimes.com,imore.com,informationweek.com,infoworld.com,itworld.com,kioskea.net,laptopmag.com,leadpages.net,lifehacker.com,mashable.com,networkworld.com,news.cnet.com,nwc.com,pastebin.com,pcadvisor.co.uk,pcmag.com,pcworld.com,phonearena.com,reviewed.com,serverfault.com,siteadvisor.com,slashdot.org,techcrunch.com,techdirt.com,techhive.com,technewsworld.com,techrepublic.com,techweb.com,tomsguide.com,tomshardware.com,ubergizmo.com,venturebeat.com,wired.com,xda-developers.com,zdnet.com',
-    'technology news' ],
-  [ 'bestbuy.ca,bestbuy.com,cdw.com,compusa.com,computerlivehelp.co,cyberguys.com,dell.com,digitalinsight.com,directron.com,ebuyer.com,frontierpc.com,frys-electronics-ads.com,frys.com,geeks.com,gyazo.com,homestead.com,lenovo.com,macmall.com,microcenter.com,miniinthebox.com,mwave.com,newegg.com,officedepot.com,outletpc.com,outpost.com,radioshack.com,rakuten.com,tigerdirect.com',
-    'tech retail' ],
-  [ 'chat.com,fring.com,hello.firefox.com,oovoo.com,viber.com',
-    'video chat' ],
-  [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,l10n.mozilla.org,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,speckyboy.com,stackoverflow.com,status.modern.ie,validator.w3.org,w3.org,webreference.com,whatcanidoformozilla.org',
-    'web development' ],
-  [ 'classroom.google.com,codeacademy.org,codecademy.com,codeschool.com,codeyear.com,elearning.ut.ac.id,how-to-build-websites.com,htmlcodetutorial.com,htmldog.com,htmlplayground.com,learn.jquery.com,quackit.com,roseindia.net,teamtreehouse.com,tizag.com,tutorialspoint.com,udacity.com,w3schools.com,webdevelopersnotes.com',
-    'webdev education' ],
-  [ 'att.com,att.net,attonlineoffers.com,bell.ca,bellsouth.com,cableone.net,cablevision.com,centurylink.com,centurylink.net,centurylinkquote.com,charter-business.com,charter.com,charter.net,chartercabledeals.com,chartermedia.com,comcast.com,comcast.net,cox.com,cox.net,coxnewsweb.com,directv.com,dish.com,dishnetwork.com,freeconferencecall.com,frontier.com,hughesnet.com,liveitwithcharter.com,mycenturylink.com,mydish.com,net10.com,officialtvstream.com.es,optimum.com,optimum.net,paygonline.com,paytm.com,qwest.com,rcn.com,rebtel.com,ringcentral.com,straighttalkbyop.com,swappa.com,textem.net,timewarner.com,timewarnercable.com,tracfone.com,verizon.com,verizon.net,voipo.com,vonagebusiness.com,wayport.net,whistleout.com,wildblue.net,windstream.net,windstreambusiness.net,wowway.com,ww2.cox.com,xfinity.com',
-    'telecommunication' ],
-  [ 'alltel.com,assurancewireless.com,attsavings.com,boostmobile.com,boostmobilestore.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,motorola.com,mycricket.com,myfamilymobile.com,nextel.com,nokia.com,nokiausa.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,sprintpcs.com,straighttalk.com,t-mobile.co.uk,t-mobile.com,tmobile.com,tracfonewireless.com,uscellular.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vodaphone.co.uk,vonange.com,vzwshop.com,wireless.att.com',
-    'mobile carrier' ],
-  [ 'aa.com,aerlingus.com,airasia.com,aircanada.com,airfrance.com,airindia.com,alaskaair.com,alaskaairlines.com,allegiantair.com,britishairways.com,cathaypacific.com,china-airlines.com,continental.com,delta.com,deltavacations.com,dragonair.com,easyjet.com,elal.co.il,emirates.com,flightaware.com,flyfrontier.com,frontierairlines.com,hawaiianair.com,iberia.com,jetairways.com,jetblue.com,klm.com,koreanair.com,kuwait-airways.com,lan.com,lufthansa.com,malaysiaairlines.com,mihinlanka.com,nwa.com,qantas.com.au,qatarairways.com,ryanair.com,singaporeair.com,smartfares.com,southwest.com,southwestvacations.com,spiritair.com,spiritairlines.com,thaiair.com,united.com,usairways.com,virgin-atlantic.com,virginamerica.com,virginblue.com.au',
-    'travel & airline' ],
-  [ 'carnival.com,celebrity-cruises.com,celebritycruises.com,costacruise.com,cruise.com,cruiseamerica.com,cruisecritic.com,cruisedirect.com,cruisemates.com,cruises.com,cruisesonly.com,crystalcruises.com,cunard.com,disneycruise.disney.go.com,hollandamerica.com,ncl.com,pocruises.com,princess.com,royalcaribbean.com,royalcaribbean.cruiselines.com,rssc.com,seabourn.com,silversea.com,starcruises.com,vikingrivercruises.com,windstarcruises.com',
-    'travel & cruise' ],
-  [ 'agoda.com,airbnb.com,beaches.com,bedandbreakfast.com,bestwestern.com,booking.com,caesars.com,choicehotels.com,comfortinn.com,daysinn.com,dealbase.com,doubletree3.hilton.com,embassysuites.com,fairmont.com,flipkey.com,fourseasons.com,greatwolf.com,hamptoninn.hilton.com,hamptoninn3.hilton.com,hhonors3.hilton.com,hilton.com,hiltongardeninn3.hilton.com,hiltonworldwide.com,holidayinn.com,homeaway.com,hotelclub.com,hotelopia.com,hotels.com,hotelscombined.com,hyatt.com,ihg.com,laterooms.com,lhw.com,lq.com,mandarinoriental.com,marriott.com,motel6.com,omnihotels.com,radisson.com,ramada.com,rci.com,reservationcounter.com,resortvacationstogo.com,ritzcarlton.com,roomkey.com,sheraton.com,starwoodhotels.com,starwoodhotelshawaii.com,super8.com,thetrain.com,vacationhomerentals.com,vacationrentals.com,vrbo.com,wyndhamrewards.com',
-    'hotel & resort' ],
-  [ 'airfarewatchdog.com,airliners.net,atlanta-airport.com,budgettravel.com,cntraveler.com,cntraveller.com,destination360.com,flightstats.com,flyertalk.com,fodors.com,frommers.com,letsgo.com,lonelyplanet.com,matadornetwork.com,perfectvacation.co,ricksteves.com,roughguides.com,timeout.com,travelalberta.us,travelandleisure.com,travelchannel.com,traveler.nationalgeographic.com,travelmath.com,traveltune.com,tripadvisor.com,vegas.com,viator.com,virtualtourist.com,wikitravel.org,worldtravelguide.net',
-    'travel' ],
-  [ 'aavacations.com,applevacations.com,avianca.com,bookingbuddy.com,bookit.com,cheapair.com,cheapcaribbean.com,cheapflights.com,cheapoair.com,cheaptickets.com,chinahighlights.com,costcotravel.com,ctrip.com,despegar.com,edreams.net,expedia.ca,expedia.com,fareboom.com,farebuzz.com,farecast.live.com,farecompare.com,faregeek.com,flightnetwork.com,funjet.com,golastminute.com,hipmunk.com,hotwire.com,ifly.com,justairticket.com,kayak.com,lastminute.com,lastminutetravel.com,lowestfare.com,lowfares.com,momondo.com,onetime.com,onetravel.com,orbitz.com,otel.com,priceline.com,pricelinevisa.com,sidestep.com,skyscanner.com,smartertravel.com,statravel.com,tigerair.com,travelocity.com,travelonbids.com,travelzoo.com,tripsta.com,trivago.com,universalorlando.com,universalstudioshollywood.com,vacationexpress.com,venere.com,webjet.com,yatra.com',
-    'travel' ],
-  [ 'airportrentalcars.com,alamo.com,amtrak.com,anytransitguide.com,avis.com,boltbus.com,budget.com,carrentalexpress.com,carrentals.com,coachusa.com,dollar.com,e-zrentacar.com,enterprise.com,europcar.com,foxrentacar.com,gotobus.com,greyhound.com,hertz.com,hertzondemand.com,indianrail.gov.in,irctc.co.in,megabus.com,mta.info,nationalcar.com,nationalrail.co.uk,njtransit.com,paylesscar.com,paylesscarrental.com,peterpanbus.com,raileurope.com,rentalcars.com,rideuta.com,stagecoachbus.com,thrifty.com,uber.com,wanderu.com,zipcar.com',
-    'travel & transit' ],
-  [ 'bulbagarden.net,cheatcc.com,cheatmasters.com,cheats.ign.com,comicvine.com,computerandvideogames.com,counter-strike.net,escapistmagazine.com,gamedaily.com,gamefront.com,gameinformer.com,gamerankings.com,gamespot.com,gamesradar.com,gamestop.com,gametrailers.com,gamezone.com,giantbomb.com,ign.com,kotaku.com,metacritic.com,minecraft-server-list.com,minecraftforge.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mmo-champion.com,mojang.com,pcgamer.com,planetminecraft.com,supercheats.com,thesims.com,totaljerkface.com,unity3d.com,vg247.com,wowhead.com',
-    'gaming' ],
-  [ 'a10.com,absolutist.com,addictinggames.com,aeriagames.com,agame.com,alpha-wars.com,arcadeyum.com,armorgames.com,ballerarcade.com,battle.net,battlefield.com,bigfishgames.com,bioware.com,bitrhymes.com,candystand.com,conjurorthegame.com,crazymonkeygames.com,crusharcade.com,curse.com,cuttherope.net,dreammining.com,dressupgames.com,ea.com,easports.com,fps-pb.com,freearcade.com,freeonlinegames.com,friv.com,funplusgame.com,gamefly.com,gameforge.com,gamehouse.com,gamejolt.com,gameloft.com,gameoapp.com,gamepedia.com,gamersfirst.com,games.com,games.yahoo.com,gamesgames.com,gamezhero.com,gamingwonderland.com,ganymede.eu,goodgamestudios.com,gpotato.com,gsn.com,guildwars2.com,hirezstudios.com,igg.com,iwin.com,kahoot.it,king.com,kizi.com,kongregate.com,leagueoflegends.com,lolking.net,maxgames.com,minecraft-mp.com,minecraft.net,miniclip.com,mmo-play.com,mmorpg.com,mobafire.com,moviestarplanet.com,myonlinearcade.com,needforspeed.com,newgrounds.com,nexusmods.com,nintendo.com,noxxic.com,onrpg.com,origin.com,pch.com,peakgames.net,playstation.com,pogo.com,pokemon.com,popcap.com,primarygames.com,r2games.com,railnation.us,riotgames.com,roblox.com,rockstargames.com,runescape.com,shockwave.com,silvergames.com,spore.com,steamcommunity.com,steampowered.com,stickpage.com,swtor.com,tetrisfriends.com,thegamerstop.com,thesims3.com,twitch.tv,warthunder.com,wildtangent.com,worldoftanks.com,worldofwarcraft.com,worldofwarplanes.com,worldofwarships.com,xbox.com,y8.com,zone.msn.com,zynga.com,zyngawithfriends.com',
-    'online gaming' ],
-]);
-
 // Only allow link urls that are http(s)
 const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
 
 // Only allow link image urls that are https or data
 const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
 
 // Only allow urls to Mozilla's CDN or empty (for data URIs)
 const ALLOWED_URL_BASE = new Set(["mozilla.net", ""]);
@@ -764,24 +592,16 @@ let DirectoryLinksProvider = {
    */
   getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
     // Use the provided link if it's already enhanced
     return link.enhancedImageURI && link ? link :
            this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
   },
 
   /**
-   * Get the display name of an allowed frecent sites. Returns undefined for a
-   * unallowed frecent sites.
-   */
-  getFrecentSitesName(sites) {
-    return ALLOWED_FRECENT_SITES.get(sites.join(","));
-  },
-
-  /**
    * Check if a url's scheme is in a Set of allowed schemes and if the base
    * domain is allowed.
    * @param url to check
    * @param allowed Set of allowed schemes
    * @param checkBase boolean to check the base domain
    */
   isURLAllowed(url, allowed, checkBase) {
     // Assume no url is an allowed url
@@ -832,28 +652,27 @@ let DirectoryLinksProvider = {
       let validityFilter = function(link) {
         // Make sure the link url is allowed and images too if they exist
         return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES, false) &&
                this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES, checkBase) &&
                this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES, checkBase);
       }.bind(this);
 
       rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
-        // Only allow suggested links with approved frecent sites
-        let name = this.getFrecentSitesName(link.frecent_sites);
-        if (name == undefined) {
+        // Suggested sites must have an adgroup name.
+        if (!link.adgroup_name) {
           return;
         }
 
         let sanitizeFlags = ParserUtils.SanitizerCidEmbedsOnly |
           ParserUtils.SanitizerDropForms |
           ParserUtils.SanitizerDropNonCSSPresentation;
 
         link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "");
-        link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0) || name);
+        link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0));
         link.lastVisitDate = rawLinks.suggested.length - position;
         // check if link wants to avoid inadjacent sites
         if (link.check_inadjacency) {
           this._avoidInadjacentSites = true;
         }
 
         // We cache suggested tiles here but do not push any of them in the links list yet.
         // The decision for which suggested tile to include will be made separately.
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -62,49 +62,53 @@ const BinaryInputStream = CC("@mozilla.o
                               "setInputStream");
 
 let gLastRequestPath;
 
 let suggestedTile1 = {
   url: "http://turbotax.com",
   type: "affiliate",
   lastVisitDate: 4,
+  adgroup_name: "Adgroup1",
   frecent_sites: [
     "taxact.com",
     "hrblock.com",
     "1040.com",
     "taxslayer.com"
   ]
 };
 let suggestedTile2 = {
   url: "http://irs.gov",
   type: "affiliate",
   lastVisitDate: 3,
+  adgroup_name: "Adgroup2",
   frecent_sites: [
     "taxact.com",
     "hrblock.com",
     "freetaxusa.com",
     "taxslayer.com"
   ]
 };
 let suggestedTile3 = {
   url: "http://hrblock.com",
   type: "affiliate",
   lastVisitDate: 2,
+  adgroup_name: "Adgroup3",
   frecent_sites: [
     "taxact.com",
     "freetaxusa.com",
     "1040.com",
     "taxslayer.com"
   ]
 };
 let suggestedTile4 = {
   url: "http://sponsoredtile.com",
   type: "sponsored",
   lastVisitDate: 1,
+  adgroup_name: "Adgroup4",
   frecent_sites: [
     "sponsoredtarget.com"
   ]
 }
 let suggestedTile5 = {
   url: "http://eviltile.com",
   type: "affiliate",
   lastVisitDate: 5,
@@ -291,19 +295,16 @@ add_task(function test_updateSuggestedTi
 
   // Initial setup
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   let testObserver = new TestFirstRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -408,26 +409,22 @@ add_task(function test_updateSuggestedTi
   topSites = [];
   testObserver = new TestRemovingSuggestedTile();
   DirectoryLinksProvider.addObserver(testObserver);
   DirectoryLinksProvider.onManyLinksChanged();
   yield testObserver.promise;
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
 add_task(function test_suggestedLinksMap() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "testing map";
-
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   // Ensure the suggested tiles were not considered directory tiles.
   do_check_eq(links.length, 1);
@@ -447,30 +444,26 @@ add_task(function test_suggestedLinksMap
   let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
   do_check_eq(suggestedSites.indexOf("sponsoredtarget.com"), 5);
   do_check_eq(suggestedSites.length, Object.keys(expected_data).length);
 
   DirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => {
     let suggestedLinksItr = suggestedLinks.values();
     for (let link of expected_data[site]) {
       let linkCopy = JSON.parse(JSON.stringify(link));
-      linkCopy.targetedName = "testing map";
+      linkCopy.targetedName = link.adgroup_name;
       linkCopy.explanation = "";
       isIdentical(suggestedLinksItr.next().value, linkCopy);
     }
   })
 
   yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
 });
 
 add_task(function test_topSitesWithSuggestedLinks() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
   // Mock out getProviderLinks() so we don't have to populate cache in NewTabUtils
   let origGetProviderLinks = NewTabUtils.getProviderLinks;
@@ -508,40 +501,41 @@ add_task(function test_topSitesWithSugge
 
   // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks.
   topSites.push(popped);
   expectedTopSitesWithSuggestedLinks.push(popped);
   DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
   isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
 });
 
 add_task(function test_suggestedAttributes() {
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let frecent_sites = "addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org".split(",");
   let imageURI = "https://image/";
   let title = "the title";
   let type = "affiliate";
   let url = "http://test.url/";
+  let adgroup_name = "Mozilla";
   let data = {
     suggested: [{
       frecent_sites,
       imageURI,
       title,
       type,
-      url
+      url,
+      adgroup_name
     }]
   };
   let dataURI = "data:application/json," + escape(JSON.stringify(data));
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   // Wait for links to get loaded
   let gLinks = NewTabUtils.links;
@@ -570,32 +564,31 @@ add_task(function test_suggestedAttribut
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
 });
 
 add_task(function test_frequencyCappedSites_views() {
   Services.prefs.setCharPref(kPingUrlPref, "");
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
   let targets = ["top.site.com"];
   let data = {
     suggested: [{
       type: "affiliate",
       frecent_sites: targets,
       url: testUrl,
-      frequency_caps: {daily: 5}
+      frequency_caps: {daily: 5},
+      adgroup_name: "Test"
     }],
     directory: [{
       type: "organic",
       url: "http://directory.site/"
     }]
   };
   let dataURI = "data:application/json," + JSON.stringify(data);
 
@@ -639,41 +632,39 @@ add_task(function test_frequencyCappedSi
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("organic", 1);
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
 add_task(function test_frequencyCappedSites_click() {
   Services.prefs.setCharPref(kPingUrlPref, "");
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
   let targets = ["top.site.com"];
   let data = {
     suggested: [{
       type: "affiliate",
       frecent_sites: targets,
-      url: testUrl
+      url: testUrl,
+      adgroup_name: "Test"
     }],
     directory: [{
       type: "organic",
       url: "http://directory.site/"
     }]
   };
   let dataURI = "data:application/json," + JSON.stringify(data);
 
@@ -711,17 +702,16 @@ add_task(function test_frequencyCappedSi
   // Make sure the link disappears after the first click
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("click");
   checkFirstTypeAndLength("organic", 1);
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
 add_task(function test_reportSitesAction() {
@@ -1212,26 +1202,24 @@ add_task(function test_DirectoryLinksPro
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   links = yield fetchData();
   do_check_eq(links.length, 0); // There are no directory links.
   checkEnhanced("http://example.net", undefined);
   checkEnhanced("http://example.com", "data:,fresh");
 });
 
 add_task(function test_DirectoryLinksProvider_enhancedURIs() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let data = {
     "suggested": [
-      {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", frecent_sites: ["test.com"]}
+      {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", adgroup_name: "Test", frecent_sites: ["test.com"]}
     ],
     "directory": [
       {url: "http://example.net", enhancedImageURI: "data:,net2", title:"DirectoryTitle"}
     ]
   };
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
@@ -1257,17 +1245,16 @@ add_task(function test_DirectoryLinksPro
 
   // Check that the suggested tile with the same URL replaces the directory tile.
   links = gLinks.getLinks();
   do_check_eq(links.length, 1);
   do_check_eq(links[0].title, "SuggestedTitle");
   do_check_eq(links[0].enhancedImageURI, "data:,net1");
 
   // Cleanup.
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
 });
 
 add_task(function test_DirectoryLinksProvider_setDefaultEnhanced() {
   function checkDefault(expected) {
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
@@ -1307,19 +1294,16 @@ add_task(function test_timeSensetiveSugg
   // Initial setup
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   let testObserver = new TestTimingRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -1419,17 +1403,16 @@ add_task(function test_timeSensetiveSugg
     do_check_true(DirectoryLinksProvider._campaignTimeoutID);
     DirectoryLinksProvider._clearCampaignTimeout();
     deferred.resolve();
   });
   yield deferred.promise;
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
 add_task(function test_setupStartEndTime() {
   let currentTime = Date.now();
   let dt = new Date(currentTime);
@@ -1723,20 +1706,16 @@ add_task(function test_DirectoryLinksPro
 
 add_task(function test_DirectoryLinksProvider_anonymous() {
   do_check_true(DirectoryLinksProvider._newXHR().mozAnon);
 });
 
 add_task(function test_sanitizeExplanation() {
   // Note: this is a basic test to ensure we applied sanitization to the link explanation.
   // Full testing for appropriate sanitization is done in parser/xml/test/unit/test_sanitizer.js.
-
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "testing map";
-
   let data = {"suggested": [suggestedTile5]};
   let dataURI = 'data:application/json,' + encodeURIComponent(JSON.stringify(data));
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
   do_check_eq(suggestedSites.indexOf("eviltarget.com"), 0);
@@ -1755,19 +1734,16 @@ add_task(function test_inadjecentSites()
   // Initial setup
   let topSites = ["1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   let testObserver = new TestFirstRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
-
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -1913,38 +1889,36 @@ add_task(function test_inadjecentSites()
   do_check_false(DirectoryLinksProvider._isInadjacentLink({}));
 
   // test _checkForInadjacentSites
   do_check_true(DirectoryLinksProvider._checkForInadjacentSites());
 
   // Cleanup
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider._inadjacentSitesUrl = origInadjacentSitesUrl;
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_reportPastImpressions() {
-  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
-  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
   let targets = ["top.site.com"];
   let data = {
     suggested: [{
       type: "affiliate",
       frecent_sites: targets,
-      url: testUrl
+      url: testUrl,
+      adgroup_name: "Test"
     }]
   };
   let dataURI = "data:application/json," + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   // make DirectoryLinksProvider load json
   let loadPromise = Promise.defer();
   DirectoryLinksProvider.getLinks(_ => {loadPromise.resolve();});
@@ -2021,12 +1995,11 @@ add_task(function test_reportPastImpress
   yield sendPingAndTest("click", "unpin", 2);
   sites.isPinned = _ => false;
   yield sendPingAndTest("click", "click", 2);
   sites.isPinned = _ => false;
   yield sendPingAndTest("click", "block", 2);
 
   // Cleanup.
   done = true;
-  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
--- a/browser/themes/shared/devtools/performance.css
+++ b/browser/themes/shared/devtools/performance.css
@@ -584,16 +584,24 @@ menuitem.marker-color-graphs-grey:before
 
 #jit-optimizations-view {
   width: 350px;
   overflow-x: hidden;
   overflow-y: auto;
   min-width: 200px;
 }
 
+#optimizations-graph {
+  height: 30px;
+}
+
+#jit-optimizations-view.empty #optimizations-graph {
+  display: none !important;
+}
+
 /* override default styles for tree widget */
 #jit-optimizations-view .tree-widget-empty-text {
   font-size: inherit;
   padding: 0px;
   margin: 8px;
 }
 
 #jit-optimizations-view:not(.empty) .tree-widget-empty-text {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -47,23 +47,23 @@
   --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
 
   --panel-separator-color: ThreeDLightShadow;
 
   --urlbar-separator-color: hsla(0,0%,16%,.2);
 }
 
 #nav-bar[brighttext] {
-  --toolbarbutton-hover-background: rgba(255,255,255,.1);
-  --toolbarbutton-hover-bordercolor: rgba(255,255,255,.1);
+  --toolbarbutton-hover-background: rgba(255,255,255,.15);
+  --toolbarbutton-hover-bordercolor: rgba(255,255,255,.15);
   --toolbarbutton-hover-boxshadow: none;
 
-  --toolbarbutton-active-background: rgba(255,255,255,.15);
-  --toolbarbutton-active-bordercolor: rgba(255,255,255,.15);
-  --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.15) inset;
+  --toolbarbutton-active-background: rgba(255,255,255,.3);
+  --toolbarbutton-active-bordercolor: rgba(255,255,255,.3);
+  --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset;
 }
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
@@ -837,18 +837,18 @@ toolbarbutton[constrain-size="true"][cui
 }
 
 #nav-bar #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
   /* horizontal padding + border + actual icon width */
   width: 31px;
 }
 
 #nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
-  padding-top: 8px;
-  padding-bottom: 8px;
+  padding-top: calc(var(--toolbarbutton-vertical-inner-padding) + 6px);
+  padding-bottom: calc(var(--toolbarbutton-vertical-inner-padding) + 6px);
 }
 
 #nav-bar .toolbaritem-combined-buttons {
   margin-left: 2px;
   margin-right: 2px;
 }
 
 #nav-bar .toolbaritem-combined-buttons > .toolbarbutton-1 {
new file mode 100644
--- /dev/null
+++ b/dom/apps/AndroidUtils.jsm
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { interfaces: Ci, utils: Cu } = Components;
+
+this.EXPORTED_SYMBOLS = ["AndroidUtils"];
+
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
+                                  "resource://gre/modules/Messaging.jsm");
+
+let appsRegistry = null;
+
+function debug() {
+  //dump("-*- AndroidUtils " + Array.slice(arguments) + "\n");
+}
+
+// Helper functions to manage Android native apps. We keep them in the
+// registry with a `kind` equals to "android-native" and we also store
+// the package name and class name in the registry.
+// Communication with the android side happens through json messages.
+
+this.AndroidUtils = {
+  init: function(aRegistry) {
+    appsRegistry = aRegistry;
+    Services.obs.addObserver(this, "Android:Apps:Installed", false);
+    Services.obs.addObserver(this, "Android:Apps:Uninstalled", false);
+  },
+
+  uninit: function() {
+    Services.obs.removeObserver(this, "Android:Apps:Installed");
+    Services.obs.removeObserver(this, "Android:Apps:Uninstalled");
+  },
+
+  getOriginAndManifestURL: function(aPackageName) {
+    let origin = "android://" + aPackageName.toLowerCase();
+    let manifestURL = origin + "/manifest.webapp";
+    return [origin, manifestURL];
+  },
+
+  getPackageAndClassFromManifestURL: function(aManifestURL) {
+    debug("getPackageAndClassFromManifestURL " + aManifestURL);
+    let app = appsRegistry.getAppByManifestURL(aManifestURL);
+    if (!app) {
+      debug("No app for " + aManifestURL);
+      return [];
+    }
+    return [app.android_packagename, app.android_classname];
+  },
+
+  buildAndroidAppData: function(aApp) {
+    // Use the package and class name to get a unique origin.
+    // We put the version with the normal case as part of the manifest url.
+    let [origin, manifestURL] =
+      this.getOriginAndManifestURL(aApp.packagename);
+    // TODO: Bug 1204557 to improve the icons support.
+    let manifest = {
+      name: aApp.name,
+      icons: { "96": aApp.icon }
+    }
+    debug("Origin is " + origin);
+    let appData = {
+      app: {
+        installOrigin: origin,
+        origin: origin,
+        manifest: manifest,
+        manifestURL: manifestURL,
+        manifestHash: AppsUtils.computeHash(JSON.stringify(manifest)),
+        appStatus: Ci.nsIPrincipal.APP_STATUS_INSTALLED,
+        removable: aApp.removable,
+        android_packagename: aApp.packagename,
+        android_classname: aApp.classname
+      },
+      isBrowser: false,
+      isPackage: false
+    };
+
+    return appData;
+  },
+
+  installAndroidApps: function() {
+    return Messaging.sendRequestForResult({ type: "Apps:GetList" }).then(
+      aApps => {
+        debug("Got " + aApps.apps.length + " android apps.");
+        let promises = [];
+        aApps.apps.forEach(app => {
+          debug("App is " + app.name + " removable? " + app.removable);
+          let p = new Promise((aResolveInstall, aRejectInstall) => {
+            let appData = this.buildAndroidAppData(app);
+            appsRegistry.confirmInstall(appData, null, aResolveInstall);
+          });
+          promises.push(p);
+        });
+
+        // Wait for all apps to be installed.
+        return Promise.all(promises);
+      }
+    ).then(appsRegistry._saveApps.bind(appsRegistry));
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    let data;
+    try {
+      data = JSON.parse(aData);
+    } catch(e) {
+      debug(e);
+      return;
+    }
+
+    if (aTopic == "Android:Apps:Installed") {
+      let appData = this.buildAndroidAppData(data);
+      appsRegistry.confirmInstall(appData);
+    } else if (aTopic == "Android:Apps:Uninstalled") {
+      let [origin, manifestURL] =
+        this.getOriginAndManifestURL(data.packagename);
+      appsRegistry.uninstall(manifestURL);
+    }
+  },
+}
--- a/dom/apps/AppsUtils.jsm
+++ b/dom/apps/AppsUtils.jsm
@@ -125,16 +125,20 @@ function _setAppProperties(aObj, aApp) {
   aObj.storeId = aApp.storeId || "";
   aObj.storeVersion = aApp.storeVersion || 0;
   aObj.role = aApp.role || "";
   aObj.redirects = aApp.redirects;
   aObj.widgetPages = aApp.widgetPages || [];
   aObj.kind = aApp.kind;
   aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
   aObj.sideloaded = aApp.sideloaded;
+#ifdef MOZ_B2GDROID
+  aObj.android_packagename = aApp.android_packagename;
+  aObj.android_classname = aApp.android_classname;
+#endif
 }
 
 this.AppsUtils = {
   // Clones a app, without the manifest.
   cloneAppObject: function(aApp) {
     let obj = {};
     _setAppProperties(obj, aApp);
     return obj;
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -86,16 +86,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Langpacks.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ImportExport",
                                   "resource://gre/modules/ImportExport.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
+                                  "resource://gre/modules/Messaging.jsm");
+
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
   Cu.import("resource://gre/modules/systemlibs.js");
   return libcutils;
 });
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
@@ -190,16 +193,17 @@ XPCOMUtils.defineLazyGetter(this, "permM
 // store even by error.
 const STORE_ID_PENDING_PREFIX = "#unknownID#";
 
 this.DOMApplicationRegistry = {
   // pseudo-constants for the different application kinds.
   get kPackaged()       "packaged",
   get kHosted()         "hosted",
   get kHostedAppcache() "hosted-appcache",
+  get kAndroid()        "android-native",
 
   // Path to the webapps.json file where we store the registry data.
   appsFile: null,
   webapps: { },
   allAppsLaunchable: false,
   _updateHandlers: [ ],
   _pendingUninstalls: {},
   _contentActions: new Map(),
@@ -254,16 +258,21 @@ this.DOMApplicationRegistry = {
 
     this.loadAndUpdateApps();
 
     Langpacks.registerRegistryFunctions(MessageBroadcaster.broadcastMessage.bind(MessageBroadcaster),
                                         this._appIdForManifestURL.bind(this),
                                         this.getFullAppByManifestURL.bind(this));
 
     MessageBroadcaster.init(this.getAppByManifestURL);
+
+    if (AppConstants.MOZ_B2GDROID) {
+      Cu.import("resource://gre/modules/AndroidUtils.jsm");
+      AndroidUtils.init(this);
+    }
   },
 
   // loads the current registry, that could be empty on first run.
   loadCurrentRegistry: function() {
     return AppsUtils.loadJSONAsync(this.appsFile).then((aData) => {
       if (!aData) {
         return;
       }
@@ -459,17 +468,19 @@ this.DOMApplicationRegistry = {
     // Create or Update the DataStore for this app
     let results = yield this._readManifests([{ id: aId }]);
     let app = this.webapps[aId];
     this.updateDataStore(app.localId, app.origin, app.manifestURL,
                          results[0].manifest, app.appStatus);
   }),
 
   appKind: function(aApp, aManifest) {
-    if (aApp.origin.startsWith("app://")) {
+    if (aApp.origin.startsWith("android://")) {
+      return this.kAndroid;
+    } if (aApp.origin.startsWith("app://")) {
       return this.kPackaged;
     } else {
       // Hosted apps, can be appcached or not.
       let kind = this.kHosted;
       if (aManifest.appcache_path) {
         kind = this.kHostedAppcache;
       }
       return kind;
@@ -517,17 +528,20 @@ this.DOMApplicationRegistry = {
           appcache_path: fullAppcachePath
         });
       }
     });
   },
 
   // Installs a 3rd party app.
   installPreinstalledApp: function installPreinstalledApp(aId) {
-#ifdef MOZ_WIDGET_GONK
+    if (!AppConstants.MOZ_B2GDROID && AppConstants.platform !== "gonk") {
+      return false;
+    }
+
     // In some cases, the app might be already installed under a different ID but
     // with the same manifestURL. In that case, the only content of the webapp will
     // be the id of the old version, which is the one we'll keep.
     let destId  = this.webapps[aId].oldId || aId;
     // We don't need the oldId anymore
     if (destId !== aId) {
       delete this.webapps[aId];
     }
@@ -620,17 +634,16 @@ this.DOMApplicationRegistry = {
       // If we are unable to extract the manifest, cleanup and remove this app.
       debug("Cleaning up: " + e);
       destDir.remove(true);
       delete this.webapps[destId];
     } finally {
       zipReader.close();
     }
     return isPreinstalled;
-#endif
   },
 
   // For hosted apps, uninstall an app served from http:// if we have
   // one installed from the same url with an https:// scheme.
   removeIfHttpsDuplicate: function(aId) {
 #ifdef MOZ_WIDGET_GONK
     let app = this.webapps[aId];
     if (!app || !app.origin.startsWith("http://")) {
@@ -787,29 +800,33 @@ this.DOMApplicationRegistry = {
             debug("Skipping app migration.");
           }
         }
 
         if (AppConstants.MOZ_B2GDROID || AppConstants.MOZ_B2G) {
           yield this.installSystemApps();
         }
 
+        if (AppConstants.MOZ_B2GDROID) {
+          yield AndroidUtils.installAndroidApps();
+        }
+
         // At first run, install preloaded apps and set up their permissions.
         for (let id in this.webapps) {
           let isPreinstalled = this.installPreinstalledApp(id);
           this.removeIfHttpsDuplicate(id);
           if (!this.webapps[id]) {
             continue;
           }
           this.updateOfflineCacheForApp(id);
           this.updatePermissionsForApp(id, isPreinstalled);
         }
         // Need to update the persisted list of apps since
         // installPreinstalledApp() removes the ones failing to install.
-        this._saveApps();
+        yield this._saveApps();
 
         Services.prefs.setBoolPref("dom.apps.reset-permissions", true);
       }
 
       // DataStores must be initialized at startup.
       for (let id in this.webapps) {
         yield this.updateDataStoreForApp(id);
       }
@@ -1190,16 +1207,19 @@ this.DOMApplicationRegistry = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "xpcom-shutdown") {
       this.messages.forEach((function(msgName) {
         ppmm.removeMessageListener(msgName, this);
       }).bind(this));
       Services.obs.removeObserver(this, "xpcom-shutdown");
       cpmm = null;
       ppmm = null;
+      if (AppConstants.MOZ_B2GDROID) {
+        AndroidUtils.uninit();
+      }
     } else if (aTopic == "memory-pressure") {
       // Clear the manifest cache on memory pressure.
       this._manifestCache = {};
     }
   },
 
   formatMessage: function(aData) {
     let msg = aData;
@@ -1299,32 +1319,32 @@ this.DOMApplicationRegistry = {
       return;
     }
 
     // For all the rest (asynchronous), we wait till the registry is ready
     // before processing the message.
     this.registryReady.then( () => {
       switch (aMessage.name) {
         case "Webapps:Install": {
-#ifdef MOZ_WIDGET_ANDROID
-          Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
-#else
-          this.doInstall(msg, mm);
-#endif
+          if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
+            Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
+          } else {
+            this.doInstall(msg, mm);
+          }
           break;
         }
         case "Webapps:GetSelf":
           this.getSelf(msg, mm);
           break;
         case "Webapps:Uninstall":
-#ifdef MOZ_WIDGET_ANDROID
-          Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg));
-#else
-          this.doUninstall(msg, mm);
-#endif
+          if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
+            Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg));
+          } else {
+            this.doUninstall(msg, mm);
+          }
           break;
         case "Webapps:Launch":
           this.doLaunch(msg, mm);
           break;
         case "Webapps:LocationChange":
           this.onLocationChange(msg.oid);
           break;
         case "Webapps:CheckInstalled":
@@ -1332,21 +1352,21 @@ this.DOMApplicationRegistry = {
           break;
         case "Webapps:GetInstalled":
           this.getInstalled(msg, mm);
           break;
         case "Webapps:GetNotInstalled":
           this.getNotInstalled(msg, mm);
           break;
         case "Webapps:InstallPackage": {
-#ifdef MOZ_WIDGET_ANDROID
-          Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
-#else
-          this.doInstallPackage(msg, mm);
-#endif
+          if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
+            Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
+          } else {
+            this.doInstallPackage(msg, mm);
+          }
           break;
         }
         case "Webapps:Download":
           this.startDownload(msg.manifestURL);
           break;
         case "Webapps:CancelDownload":
           this.cancelDownload(msg.manifestURL);
           break;
@@ -1597,16 +1617,29 @@ this.DOMApplicationRegistry = {
 
     // Fire an error when trying to launch an app that is not
     // yet fully installed.
     if (app.installState == "pending") {
       aOnFailure("PENDING_APP_NOT_LAUNCHABLE");
       return;
     }
 
+    // Delegate native android apps launch.
+    if (this.kAndroid == app.kind) {
+      debug("Launching android app " + app.origin);
+      let [packageName, className] =
+        AndroidUtils.getPackageAndClassFromManifestURL(aManifestURL);
+      debug("  " + packageName + " " + className);
+      Messaging.sendRequest({ type: "Apps:Launch",
+                              packagename: packageName,
+                              classname: className });
+      aOnSuccess();
+      return;
+    }
+
     // We have to clone the app object as nsIDOMApplication objects are
     // stringified as an empty object. (see bug 830376)
     let appClone = AppsUtils.cloneAppObject(app);
     appClone.startPoint = aStartPoint;
     appClone.timestamp = aTimeStamp;
     Services.obs.notifyObservers(null, "webapps-launch", JSON.stringify(appClone));
     aOnSuccess();
   },
@@ -2041,18 +2074,19 @@ this.DOMApplicationRegistry = {
 
     // We may be able to remove this when Bug 839071 is fixed.
     if (app.downloading) {
       sendError("APP_IS_DOWNLOADING");
       return;
     }
 
     // If the app is packaged and its manifestURL has an app:// scheme,
-    // then we can't have an update.
-    if (app.kind == this.kPackaged && app.manifestURL.startsWith("app://")) {
+    // or if it's a native Android app then we can't have an update.
+    if (app.kind == this.kAndroid ||
+        (app.kind == this.kPackaged && app.manifestURL.startsWith("app://"))) {
       sendError("NOT_UPDATABLE");
       return;
     }
 
     // For non-removable hosted apps that lives in the core apps dir we
     // only check the appcache because we can't modify the manifest even
     // if it has changed.
     let onlyCheckAppCache = false;
@@ -2774,18 +2808,20 @@ this.DOMApplicationRegistry = {
         this.revertDownloadPackage(id, oldApp, newApp, false, ex);
       }
     }
   }),
 
   _setupApp: function(aData, aId) {
     let app = aData.app;
 
-    // app can be uninstalled
-    app.removable = true;
+    // app can be uninstalled by default.
+    if (app.removable === undefined) {
+      app.removable = true;
+    }
 
     if (aData.isPackage) {
       // Override the origin with the correct id.
       app.origin = "app://" + aId;
     }
 
     app.id = aId;
     app.installTime = Date.now();
@@ -2808,17 +2844,18 @@ this.DOMApplicationRegistry = {
       appObject.downloadSize = 0;
       appObject.readyToApplyDownload = false;
     } else if (appObject.kind == this.kPackaged) {
       appObject.installState = "pending";
       appObject.downloadAvailable = true;
       appObject.downloading = true;
       appObject.downloadSize = aLocaleManifest.size;
       appObject.readyToApplyDownload = false;
-    } else if (appObject.kind == this.kHosted) {
+    } else if (appObject.kind == this.kHosted ||
+               appObject.kind == this.kAndroid) {
       appObject.installState = "installed";
       appObject.downloadAvailable = false;
       appObject.downloading = false;
       appObject.readyToApplyDownload = false;
     } else {
       debug("Unknown app kind: " + appObject.kind);
       throw Error("Unknown app kind: " + appObject.kind);
     }
@@ -2864,18 +2901,16 @@ this.DOMApplicationRegistry = {
 
     app.csp = aManifest.csp || "";
 
     let aLocaleManifest = new ManifestHelper(aManifest, app.origin, app.manifestURL);
     this._saveWidgetsFullPath(aLocaleManifest, app);
 
     app.appStatus = AppsUtils.getAppManifestStatus(aManifest);
 
-    app.removable = true;
-
     // Reuse the app ID if the scheme is "app".
     let uri = Services.io.newURI(app.origin, null, null);
     if (uri.scheme == "app") {
       app.id = uri.host;
     } else {
       app.id = this.makeAppId();
     }
 
@@ -2955,16 +2990,17 @@ this.DOMApplicationRegistry = {
       new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
     // Set the application kind.
     app.kind = this.appKind(app, manifest);
 
     let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId);
 
     this.webapps[id] = appObject;
+    this._manifestCache[id] = jsonManifest;
 
     // For package apps, the permissions are not in the mini-manifest, so
     // don't update the permissions yet.
     if (!aData.isPackage) {
       if (supportUseCurrentProfile()) {
         try {
           if (Services.prefs.getBoolPref("dom.apps.developer_mode")) {
             this.webapps[id].appStatus =
@@ -4059,22 +4095,34 @@ this.DOMApplicationRegistry = {
     // leaking the whole page associationed with the message manager.
     aMm = Cu.getWeakReference(aMm);
 
     let response = "Webapps:Uninstall:Return:OK";
 
     try {
       aData.app = yield this._getAppWithManifest(aData.manifestURL);
 
-      let prefName = "dom.mozApps.auto_confirm_uninstall";
-      if (Services.prefs.prefHasUserValue(prefName) &&
-          Services.prefs.getBoolPref(prefName)) {
-        yield this._uninstallApp(aData.app);
+      if (this.kAndroid == aData.app.kind) {
+        debug("Uninstalling android app " + aData.app.origin);
+        let [packageName, className] =
+          AndroidUtils.getPackageAndClassFromManifestURL(aData.manifestURL);
+        Messaging.sendRequest({ type: "Apps:Uninstall",
+                                packagename: packageName,
+                                classname: className });
+        // We have to wait for Android's uninstall before sending the
+        // uninstall event, so fake an error here.
+        response = "Webapps:Uninstall:Return:KO";
       } else {
-        yield this._promptForUninstall(aData);
+        let prefName = "dom.mozApps.auto_confirm_uninstall";
+        if (Services.prefs.prefHasUserValue(prefName) &&
+            Services.prefs.getBoolPref(prefName)) {
+          yield this._uninstallApp(aData.app);
+        } else {
+          yield this._promptForUninstall(aData);
+        }
       }
     } catch (error) {
       aData.error = error;
       response = "Webapps:Uninstall:Return:KO";
     }
 
     if ((aMm = aMm.get())) {
       aMm.sendAsyncMessage(response, this.formatMessage(aData));
--- a/dom/apps/moz.build
+++ b/dom/apps/moz.build
@@ -37,16 +37,21 @@ EXTRA_JS_MODULES += [
     'MessageBroadcaster.jsm',
     'OfflineCacheInstaller.jsm',
     'PermissionsInstaller.jsm',
     'PermissionsTable.jsm',
     'StoreTrustAnchor.jsm',
     'UserCustomizations.jsm',
 ]
 
+if CONFIG['MOZ_B2GDROID']:
+    EXTRA_JS_MODULES += [
+        'AndroidUtils.jsm',
+    ]
+
 EXTRA_PP_JS_MODULES += [
     'AppsUtils.jsm',
     'ImportExport.jsm',
     'InterAppCommService.jsm',
     'OperatorApps.jsm',
     'ScriptPreloader.jsm',
     'Webapps.jsm',
 ]
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -18,24 +18,24 @@ namespace devtools {
 class HeapSnapshot;
 } // namespace devtools
 
 namespace dom {
 
 class ThreadSafeChromeUtils
 {
 public:
-  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  // Implemented in toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
   static void SaveHeapSnapshot(GlobalObject& global,
                                JSContext* cx,
-                               const nsAString& filePath,
                                const HeapSnapshotBoundaries& boundaries,
+                               nsAString& filePath,
                                ErrorResult& rv);
 
-  // Implemented in toolkit/devtools/server/HeapSnapshot.cpp
+  // Implemented in toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
   static already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(GlobalObject& global,
                                                                    JSContext* cx,
                                                                    const nsAString& filePath,
                                                                    ErrorResult& rv);
 };
 
 class ChromeUtils : public ThreadSafeChromeUtils
 {
--- a/dom/system/gonk/MozMtpDatabase.cpp
+++ b/dom/system/gonk/MozMtpDatabase.cpp
@@ -17,16 +17,18 @@
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsPrintfCString.h"
 #include "nsString.h"
 #include "prio.h"
 
 #include <dirent.h>
 #include <libgen.h>
+#include <utime.h>
+#include <sys/stat.h>
 
 using namespace android;
 using namespace mozilla;
 
 namespace mozilla {
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCloseDir, PRDir, PR_CloseDir)
 }
 
@@ -58,29 +60,40 @@ static const char *
 ObjectPropertyAsStr(MtpObjectProperty aProperty)
 {
   switch (aProperty) {
     case MTP_PROPERTY_STORAGE_ID:         return "MTP_PROPERTY_STORAGE_ID";
     case MTP_PROPERTY_OBJECT_FORMAT:      return "MTP_PROPERTY_OBJECT_FORMAT";
     case MTP_PROPERTY_PROTECTION_STATUS:  return "MTP_PROPERTY_PROTECTION_STATUS";
     case MTP_PROPERTY_OBJECT_SIZE:        return "MTP_PROPERTY_OBJECT_SIZE";
     case MTP_PROPERTY_OBJECT_FILE_NAME:   return "MTP_PROPERTY_OBJECT_FILE_NAME";
+    case MTP_PROPERTY_DATE_CREATED:       return "MTP_PROPERTY_DATE_CREATED";
     case MTP_PROPERTY_DATE_MODIFIED:      return "MTP_PROPERTY_DATE_MODIFIED";
     case MTP_PROPERTY_PARENT_OBJECT:      return "MTP_PROPERTY_PARENT_OBJECT";
     case MTP_PROPERTY_PERSISTENT_UID:     return "MTP_PROPERTY_PERSISTENT_UID";
     case MTP_PROPERTY_NAME:               return "MTP_PROPERTY_NAME";
     case MTP_PROPERTY_DATE_ADDED:         return "MTP_PROPERTY_DATE_ADDED";
     case MTP_PROPERTY_WIDTH:              return "MTP_PROPERTY_WIDTH";
     case MTP_PROPERTY_HEIGHT:             return "MTP_PROPERTY_HEIGHT";
     case MTP_PROPERTY_IMAGE_BIT_DEPTH:    return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
     case MTP_PROPERTY_DISPLAY_NAME:       return "MTP_PROPERTY_DISPLAY_NAME";
   }
   return "MTP_PROPERTY_???";
 }
 
+static char*
+FormatDate(time_t aTime, char *aDateStr, size_t aDateStrSize)
+{
+  struct tm tm;
+  localtime_r(&aTime, &tm);
+  MTP_LOG("(%ld) tm_zone = %s off = %ld", aTime, tm.tm_zone, tm.tm_gmtoff);
+  strftime(aDateStr, aDateStrSize, "%Y%m%dT%H%M%S", &tm);
+  return aDateStr;
+}
+
 MozMtpDatabase::MozMtpDatabase()
   : mMutex("MozMtpDatabase::mMutex"),
     mDb(mMutex),
     mStorage(mMutex),
     mBeginSendObjectCalled(false)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
@@ -217,19 +230,31 @@ MozMtpDatabase::UpdateEntry(MtpObjectHan
 {
   MutexAutoLock lock(mMutex);
 
   RefPtr<DbEntry> entry = mDb[aHandle];
 
   int64_t fileSize = 0;
   aFile->mFile->GetFileSize(&fileSize);
   entry->mObjectSize = fileSize;
-  aFile->mFile->GetLastModifiedTime(&entry->mDateCreated);
-  entry->mDateModified = entry->mDateCreated;
-  MTP_DBG("UpdateEntry (0x%08x file %s)", entry->mHandle, entry->mPath.get());
+
+  PRTime dateModifiedMsecs;
+  // GetLastModifiedTime returns msecs
+  aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
+  entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
+  entry->mDateCreated = entry->mDateModified;
+  entry->mDateAdded = entry->mDateModified;
+
+  #if USE_DEBUG
+  char dateStr[20];
+  MTP_DBG("UpdateEntry (0x%08x file %s) modified (%ld) %s",
+          entry->mHandle, entry->mPath.get(),
+          entry->mDateModified,
+          FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+  #endif
 }
 
 
 class FileWatcherNotifyRunnable final : public nsRunnable
 {
 public:
   FileWatcherNotifyRunnable(nsACString& aStorageName,
                             nsACString& aPath,
@@ -449,24 +474,29 @@ MozMtpDatabase::CreateEntryForFileAndNot
     if (slash == kNotFound) {
       // No slash - this is the file component
       entry->mObjectFormat = MTP_FORMAT_DEFINED;
 
       int64_t fileSize = 0;
       aFile->mFile->GetFileSize(&fileSize);
       entry->mObjectSize = fileSize;
 
-      aFile->mFile->GetLastModifiedTime(&entry->mDateCreated);
+      // Note: Even though PRTime records usec, GetLastModifiedTime returns
+      //       msecs.
+      PRTime dateModifiedMsecs;
+      aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
+      entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
     } else {
       // Found a slash, this makes this a directory component
       entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
       entry->mObjectSize = 0;
-      entry->mDateCreated = PR_Now();
+      time(&entry->mDateModified);
     }
-    entry->mDateModified = entry->mDateCreated;
+    entry->mDateCreated = entry->mDateModified;
+    entry->mDateAdded = entry->mDateModified;
 
     AddEntryAndNotify(entry, aMtpServer);
     MTP_LOG("About to call sendObjectAdded Handle 0x%08x file %s", entry->mHandle, entry->mPath.get());
 
     parent = entry->mHandle;
     offset = slash + 1;
   } while (slash != kNotFound);
 
@@ -498,18 +528,21 @@ MozMtpDatabase::AddDirectory(MtpStorageI
 
     RefPtr<DbEntry> entry = new DbEntry;
 
     entry->mStorageID = aStorageID;
     entry->mParent = aParent;
     entry->mObjectName = dirEntry->name;
     entry->mDisplayName = dirEntry->name;
     entry->mPath = filename;
-    entry->mDateCreated = fileInfo.creationTime;
-    entry->mDateModified = fileInfo.modifyTime;
+
+    // PR_GetFileInfo64 returns timestamps in usecs
+    entry->mDateModified = fileInfo.modifyTime / PR_USEC_PER_SEC;
+    entry->mDateCreated = fileInfo.creationTime / PR_USEC_PER_SEC;
+    time(&entry->mDateAdded);
 
     if (fileInfo.type == PR_FILE_FILE) {
       entry->mObjectFormat = MTP_FORMAT_DEFINED;
       //TODO: Check how 64-bit filesize are dealt with
       entry->mObjectSize = fileInfo.size;
       AddEntry(entry);
     } else if (fileInfo.type == PR_FILE_DIRECTORY) {
       entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
@@ -648,19 +681,81 @@ MozMtpDatabase::beginSendObject(const ch
   entry->mStorageID = aStorageID;
   entry->mParent = aParent;
   entry->mPath = aPath;
   entry->mObjectName = BaseName(entry->mPath);
   entry->mDisplayName = entry->mObjectName;
   entry->mObjectFormat = aFormat;
   entry->mObjectSize = aSize;
 
+  if (aModified != 0) {
+    // Currently, due to the way that parseDateTime is coded in
+    // frameworks/av/media/mtp/MtpUtils.cpp, aModified winds up being the number
+    // of seconds from the epoch in local time, rather than UTC time. So we
+    // need to convert it back to being relative to UTC since that's what linux
+    // expects time_t to contain.
+    //
+    // In more concrete testable terms, if the host parses 2015-08-02 02:22:00
+    // as a local time in the Pacific timezone, aModified will come to us as
+    // 1438482120.
+    //
+    // What we want is what mktime would pass us with the same date. Using python
+    // (because its simple) with the current timezone set to be America/Vancouver:
+    //
+    // >>> import time
+    // >>> time.mktime((2015, 8, 2, 2, 22, 0, 0, 0, -1))
+    // 1438507320.0
+    // >>> time.localtime(1438507320)
+    // time.struct_time(tm_year=2015, tm_mon=8, tm_mday=2, tm_hour=2, tm_min=22, tm_sec=0, tm_wday=6, tm_yday=214, tm_isdst=1)
+    //
+    // Currently, when a file has a modification time of 2015-08-22 02:22:00 PDT
+    // then aModified will come in as 1438482120 which corresponds to
+    // 2015-08-22 02:22:00 UTC
+
+    struct tm tm;
+    if (gmtime_r(&aModified, &tm) != NULL) {
+      // GMT always comes back with tm_isdst = 0, so we set it to -1 in order
+      // to have mktime figure out dst based on the date.
+      tm.tm_isdst = -1;
+      aModified = mktime(&tm);
+      if (aModified == (time_t)-1) {
+        aModified = 0;
+      }
+    } else {
+      aModified = 0;
+    }
+  }
+  if (aModified == 0) {
+    // The ubuntu host doesn't pass in the modified/created times in the
+    // SENDOBJECT packet, so aModified winds up being zero. About the best
+    // we can do with that is to use the current time.
+    time(&aModified);
+  }
+
+  // And just an FYI for anybody else looking at timestamps. Under OSX you
+  // need to use the Android File Transfer program to copy files into the
+  // phone. That utility passes in both date modified and date created
+  // timestamps, but they're both equal to the time that the file was copied
+  // and not the times that are associated with the files.
+
+  // Now we have aModified in a traditional time_t format, which is the number
+  // of seconds from the UTC epoch.
+
+  entry->mDateModified = aModified;
+  entry->mDateCreated = entry->mDateModified;
+  entry->mDateAdded = entry->mDateModified;
+
   AddEntry(entry);
 
-  MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s'", entry->mHandle, aParent, aPath);
+  #if USE_DEBUG
+  char dateStr[20];
+  MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s' aModified %ld %s",
+          entry->mHandle, aParent, aPath, aModified,
+          FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+  #endif
 
   mBeginSendObjectCalled = true;
   return entry->mHandle;
 }
 
 // called to report success or failure of the SendObject file transfer
 // success should signal a notification of the new object's creation,
 // failure should remove the database entry created in beginSendObject
@@ -672,16 +767,32 @@ MozMtpDatabase::endSendObject(const char
                               MtpObjectFormat aFormat,
                               bool aSucceeded)
 {
   MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath);
 
   if (aSucceeded) {
     RefPtr<DbEntry> entry = GetEntry(aHandle);
     if (entry) {
+      // The android MTP server only copies the data in, it doesn't set the
+      // modified timestamp, so we do that here.
+
+      struct utimbuf new_times;
+      struct stat sb;
+
+      char dateStr[20];
+      MTP_LOG("Path: '%s' setting modified time to (%ld) %s",
+              entry->mPath.get(), entry->mDateModified,
+              FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+
+      stat(entry->mPath.get(), &sb);
+      new_times.actime = sb.st_atime;   // Preserve atime
+      new_times.modtime = entry->mDateModified;
+      utime(entry->mPath.get(), &new_times);
+
       FileWatcherNotify(entry, "modified");
     }
   } else {
     RemoveEntry(aHandle);
   }
   mBeginSendObjectCalled = false;
 }
 
@@ -789,16 +900,17 @@ MozMtpDatabase::getSupportedCaptureForma
 static const MtpObjectProperty sSupportedObjectProperties[] =
 {
   MTP_PROPERTY_STORAGE_ID,
   MTP_PROPERTY_OBJECT_FORMAT,
   MTP_PROPERTY_PROTECTION_STATUS,   // UINT16 - always 0
   MTP_PROPERTY_OBJECT_SIZE,
   MTP_PROPERTY_OBJECT_FILE_NAME,    // just the filename - no directory
   MTP_PROPERTY_NAME,
+  MTP_PROPERTY_DATE_CREATED,
   MTP_PROPERTY_DATE_MODIFIED,
   MTP_PROPERTY_PARENT_OBJECT,
   MTP_PROPERTY_PERSISTENT_UID,
   MTP_PROPERTY_DATE_ADDED,
 };
 
 //virtual
 MtpObjectPropertyList*
@@ -869,16 +981,17 @@ GetTypeOfObjectProp(MtpObjectProperty aP
   };
 
   static const PropertyTableEntry kObjectPropertyTable[] = {
     {MTP_PROPERTY_STORAGE_ID,        MTP_TYPE_UINT32  },
     {MTP_PROPERTY_OBJECT_FORMAT,     MTP_TYPE_UINT16  },
     {MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16  },
     {MTP_PROPERTY_OBJECT_SIZE,       MTP_TYPE_UINT64  },
     {MTP_PROPERTY_OBJECT_FILE_NAME,  MTP_TYPE_STR     },
+    {MTP_PROPERTY_DATE_CREATED,      MTP_TYPE_STR     },
     {MTP_PROPERTY_DATE_MODIFIED,     MTP_TYPE_STR     },
     {MTP_PROPERTY_PARENT_OBJECT,     MTP_TYPE_UINT32  },
     {MTP_PROPERTY_DISPLAY_NAME,      MTP_TYPE_STR     },
     {MTP_PROPERTY_NAME,              MTP_TYPE_STR     },
     {MTP_PROPERTY_PERSISTENT_UID,    MTP_TYPE_UINT128 },
     {MTP_PROPERTY_DATE_ADDED,        MTP_TYPE_STR     },
   };
 
@@ -1125,16 +1238,18 @@ MozMtpDatabase::getObjectPropertyList(Mt
     numObjectProperties = 1;
     objectProperty = aProperty;
     objectPropertyList = &objectProperty;
   }
 
   UnprotectedDbArray::size_type numEntries = result.Length();
   UnprotectedDbArray::index_type entryIdx;
 
+  char dateStr[20];
+
   aPacket.putUInt32(numObjectProperties * numEntries);
   for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
     RefPtr<DbEntry> entry = result[entryIdx];
 
     for (size_t propertyIdx = 0; propertyIdx < numObjectProperties; propertyIdx++) {
       aPacket.putUInt32(entry->mHandle);
       MtpObjectProperty prop = objectPropertyList[propertyIdx];
       aPacket.putUInt16(prop);
@@ -1173,33 +1288,34 @@ MozMtpDatabase::getObjectPropertyList(Mt
           aPacket.putString(entry->mObjectName.get());
           break;
 
         case MTP_PROPERTY_PROTECTION_STATUS:
           aPacket.putUInt16(MTP_TYPE_UINT16);
           aPacket.putUInt16(0); // 0 = No Protection
           break;
 
+        case MTP_PROPERTY_DATE_CREATED: {
+          aPacket.putUInt16(MTP_TYPE_STR);
+          aPacket.putString(FormatDate(entry->mDateCreated, dateStr, sizeof(dateStr)));
+          MTP_LOG("mDateCreated: (%ld) %s", entry->mDateCreated, dateStr);
+          break;
+        }
+
         case MTP_PROPERTY_DATE_MODIFIED: {
           aPacket.putUInt16(MTP_TYPE_STR);
-          PRExplodedTime explodedTime;
-          PR_ExplodeTime(entry->mDateModified, PR_LocalTimeParameters, &explodedTime);
-          char dateStr[20];
-          PR_FormatTime(dateStr, sizeof(dateStr), "%Y%m%dT%H%M%S", &explodedTime);
-          aPacket.putString(dateStr);
+          aPacket.putString(FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+          MTP_LOG("mDateModified: (%ld) %s", entry->mDateModified, dateStr);
           break;
         }
 
         case MTP_PROPERTY_DATE_ADDED: {
           aPacket.putUInt16(MTP_TYPE_STR);
-          PRExplodedTime explodedTime;
-          PR_ExplodeTime(entry->mDateCreated, PR_LocalTimeParameters, &explodedTime);
-          char dateStr[20];
-          PR_FormatTime(dateStr, sizeof(dateStr), "%Y%m%dT%H%M%S", &explodedTime);
-          aPacket.putString(dateStr);
+          aPacket.putString(FormatDate(entry->mDateAdded, dateStr, sizeof(dateStr)));
+          MTP_LOG("mDateAdded: (%ld) %s", entry->mDateAdded, dateStr);
           break;
         }
 
         default:
           MTP_ERR("Unrecognized property code: %u", prop);
           return MTP_RESPONSE_GENERAL_ERROR;
       }
     }
@@ -1238,21 +1354,24 @@ MozMtpDatabase::getObjectInfo(MtpObjectH
   aInfo.mImagePixWidth = 0;
   aInfo.mImagePixHeight = 0;
   aInfo.mImagePixDepth = 0;
   aInfo.mParent = entry->mParent;
   aInfo.mAssociationType = 0;
   aInfo.mAssociationDesc = 0;
   aInfo.mSequenceNumber = 0;
   aInfo.mName = ::strdup(entry->mObjectName.get());
+  aInfo.mDateCreated = entry->mDateCreated;
+  aInfo.mDateModified = entry->mDateModified;
 
-  // entry->mDateXxxx is a PRTime stores the time as microseconds from the epoch.
-  // aInfo.mDateXxxx is time_t which stores the time as seconds from the epoch.
-  aInfo.mDateCreated = entry->mDateCreated / PR_USEC_PER_SEC;
-  aInfo.mDateModified = entry->mDateModified / PR_USEC_PER_SEC;
+  MTP_LOG("aInfo.mDateCreated = %ld entry->mDateCreated = %ld",
+          aInfo.mDateCreated, entry->mDateCreated);
+  MTP_LOG("aInfo.mDateModified = %ld entry->mDateModified = %ld",
+          aInfo.mDateModified, entry->mDateModified);
+
   aInfo.mKeywords = ::strdup("fxos,touch");
 
   return MTP_RESPONSE_OK;
 }
 
 //virtual
 void*
 MozMtpDatabase::getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize)
@@ -1378,16 +1497,17 @@ MozMtpDatabase::getObjectPropertyDesc(Mt
       break;
     case MTP_PROPERTY_DISPLAY_NAME:
     case MTP_PROPERTY_NAME:
       result = new MtpProperty(aProperty, MTP_TYPE_STR);
       break;
     case MTP_PROPERTY_OBJECT_FILE_NAME:
       result = new MtpProperty(aProperty, MTP_TYPE_STR, true);
       break;
+    case MTP_PROPERTY_DATE_CREATED:
     case MTP_PROPERTY_DATE_MODIFIED:
     case MTP_PROPERTY_DATE_ADDED:
       result = new MtpProperty(aProperty, MTP_TYPE_STR);
       result->setFormDateTime();
       break;
     case MTP_PROPERTY_PERSISTENT_UID:
       result = new MtpProperty(aProperty, MTP_TYPE_UINT128);
       break;
--- a/dom/system/gonk/MozMtpDatabase.h
+++ b/dom/system/gonk/MozMtpDatabase.h
@@ -128,30 +128,32 @@ private:
   {
     DbEntry()
       : mHandle(0),
         mStorageID(0),
         mObjectFormat(MTP_FORMAT_DEFINED),
         mParent(0),
         mObjectSize(0),
         mDateCreated(0),
-        mDateModified(0) {}
+        mDateModified(0),
+        mDateAdded(0) {}
 
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DbEntry)
 
     MtpObjectHandle mHandle;        // uint32_t
     MtpStorageID    mStorageID;     // uint32_t
     nsCString       mObjectName;
     MtpObjectFormat mObjectFormat;  // uint16_t
     MtpObjectHandle mParent;        // uint32_t
     uint64_t        mObjectSize;
     nsCString       mDisplayName;
     nsCString       mPath;
-    PRTime          mDateCreated;
-    PRTime          mDateModified;
+    time_t          mDateCreated;
+    time_t          mDateModified;
+    time_t          mDateAdded;
 
   protected:
     ~DbEntry() {}
   };
 
   template<class T>
   class ProtectedTArray : private nsTArray<T>
   {
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -14455,17 +14455,26 @@ ICCContactHelperObject.prototype = {
       if (!pbr[field]) {
         updateField.call(this);
         return;
       }
 
       this.updateContactField(pbr, contact, field, (fieldEntry) => {
         contactField = Object.assign(contactField, fieldEntry);
         updateField.call(this);
-      }, onerror);
+      }, (errorMsg) => {
+        // Bug 1194149, there are some sim cards without sufficient
+        // Type 2 USIM contact fields record. We allow user continue
+        // importing contacts.
+        if (errorMsg === CONTACT_ERR_NO_FREE_RECORD_FOUND) {
+          updateField.call(this);
+          return;
+        }
+        onerror(errorMsg);
+      });
     }).call(this);
   },
 
   /**
    * Update contact's field from USIM.
    *
    * @param pbr           The phonebook reference file.
    * @param contact       The contact needs to be updated.
--- a/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js
@@ -602,16 +602,121 @@ add_test(function test_update_icc_contac
     do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234",
             null, true);
   }
 
   run_next_test();
 });
 
 /**
+ * Verify ICCContactHelper.updateICCContact with appType is CARD_APPTYPE_USIM and
+ * insufficient space to store Type 2 USIM contact fields.
+ */
+add_test(function test_update_icc_contact_full_email_and_anr_field() {
+  const ADN_RECORD_ID   = 100;
+  const ADN_SFI         = 1;
+  const IAP_FILE_ID     = 0x4f17;
+  const EMAIL_FILE_ID   = 0x4f50;
+  const EMAIL_RECORD_ID = 20;
+  const ANR0_FILE_ID    = 0x4f11;
+  const ANR0_RECORD_ID  = 30;
+
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let recordHelper = context.ICCRecordHelper;
+  let contactHelper = context.ICCContactHelper;
+  let ril = context.RIL;
+
+  function do_test(aSimType, aContactType, aContact, aPin2) {
+    ril.appType = CARD_APPTYPE_USIM;
+    ril.iccInfoPrivate.sst = [0x2, 0x0, 0x0, 0x0, 0x0];
+
+    recordHelper.readPBR = function(onsuccess, onerror) {
+      onsuccess([{
+        adn:   {fileId: ICC_EF_ADN,
+                sfi: ADN_SFI},
+        iap:   {fileId: IAP_FILE_ID},
+        email: {fileId: EMAIL_FILE_ID,
+                fileType: ICC_USIM_TYPE2_TAG,
+                indexInIAP: 0},
+        anr0:  {fileId: ANR0_FILE_ID,
+                fileType: ICC_USIM_TYPE2_TAG,
+                indexInIAP: 1}
+      }]);
+    };
+
+    recordHelper.updateADNLike = function(fileId, contact, pin2, onsuccess, onerror) {
+      if (aContactType === GECKO_CARDCONTACT_TYPE_ADN) {
+        equal(fileId, ICC_EF_ADN);
+      }
+      equal(pin2, aPin2);
+      equal(contact.alphaId, aContact.alphaId);
+      equal(contact.number, aContact.number);
+      onsuccess({alphaId: contact.alphaId,
+                  number: contact.number});
+    };
+
+    recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) {
+      equal(fileId, IAP_FILE_ID);
+      equal(recordNumber, ADN_RECORD_ID);
+      onsuccess([0xff, 0xff]);
+    };
+
+    recordHelper.updateIAP = function(fileId, recordNumber, iap, onsuccess, onerror) {
+      equal(fileId, IAP_FILE_ID);
+      equal(recordNumber, ADN_RECORD_ID);
+      onsuccess();
+    };
+
+    recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) {
+      let recordId = 0;
+      // emulate email and anr don't have free record.
+      if (fileId === EMAIL_FILE_ID || fileId === ANR0_FILE_ID) {
+        onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+      } else {
+        onsuccess(recordId);
+      }
+    };
+
+    let isSuccess = false;
+    let onsuccess = function onsuccess(updatedContact) {
+      equal(ADN_RECORD_ID, updatedContact.recordId);
+      equal(aContact.alphaId, updatedContact.alphaId);
+      equal(updatedContact.email, null);
+      equal(updatedContact.anr, null);
+
+      do_print("updateICCContact success");
+      isSuccess = true;
+    };
+
+    let onerror = function onerror(errorMsg) {
+      do_print("updateICCContact failed: " + errorMsg);
+    };
+
+    contactHelper.updateICCContact(aSimType, aContactType, aContact, aPin2, onsuccess, onerror);
+    ok(isSuccess);
+  }
+
+  let contact = {
+      pbrIndex: 0,
+      recordId: ADN_RECORD_ID,
+      alphaId:  "test",
+      number:   "123456",
+      email:    "test@mail.com",
+      anr:      ["+654321"]
+    };
+
+  // USIM
+  do_print("Test update USIM adn contacts");
+  do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null);
+
+  run_next_test();
+});
+
+/**
  * Verify updateICCContact with removal of anr and email with File Type 1.
  */
 add_test(function test_update_icc_contact_with_remove_type1_attr() {
   const ADN_RECORD_ID   = 100;
   const IAP_FILE_ID     = 0x4f17;
   const EMAIL_FILE_ID   = 0x4f50;
   const EMAIL_RECORD_ID = 20;
   const ANR0_FILE_ID    = 0x4f11;
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -57,18 +57,23 @@ public:
   Disconnect()
   {
     MOZ_ASSERT(mTelephony);
     mTelephony = nullptr;
   }
 };
 
 Telephony::Telephony(nsPIDOMWindow* aOwner)
-  : DOMEventTargetHelper(aOwner)
+  : DOMEventTargetHelper(aOwner),
+    mAudioAgentNotify(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY),
+    mIsAudioStartPlaying(false),
+    mHaveDispatchedInterruptBeginEvent(false),
+    mMuted(AudioChannelService::IsAudioChannelMutedByDefault())
 {
+  MOZ_ASSERT(aOwner);
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aOwner);
   MOZ_ASSERT(global);
 
   ErrorResult rv;
   nsRefPtr<Promise> promise = Promise::Create(global, rv);
   MOZ_ASSERT(!rv.Failed());
 
   mReadyPromise = promise;
@@ -513,16 +518,83 @@ Telephony::StopTone(const Optional<uint3
   if (!IsValidServiceId(serviceId)) {
     aRv.Throw(NS_ERROR_INVALID_ARG);
     return;
   }
 
   aRv = mService->StopTone(serviceId);
 }
 
+void
+Telephony::OwnAudioChannel(ErrorResult& aRv)
+{
+  if (mAudioAgent) {
+    return;
+  }
+
+  mAudioAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
+  MOZ_ASSERT(mAudioAgent);
+  aRv = mAudioAgent->Init(GetParentObject(),
+                         (int32_t)AudioChannel::Telephony, this);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+  aRv = HandleAudioAgentState();
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+}
+
+nsresult
+Telephony::HandleAudioAgentState()
+{
+  if (!mAudioAgent) {
+    return NS_OK;
+  }
+
+  Nullable<OwningTelephonyCallOrTelephonyCallGroup> activeCall;
+  GetActive(activeCall);
+  nsresult rv;
+  // Only stop the agent when there's no call.
+  if ((!mCalls.Length() && !mGroup->CallsArray().Length()) &&
+       mIsAudioStartPlaying) {
+    mIsAudioStartPlaying = false;
+    rv = mAudioAgent->NotifyStoppedPlaying(mAudioAgentNotify);
+    mAudioAgent = nullptr;
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else if (!activeCall.IsNull() && !mIsAudioStartPlaying) {
+    mIsAudioStartPlaying = true;
+    float volume;
+    bool muted;
+    rv = mAudioAgent->NotifyStartedPlaying(mAudioAgentNotify, &volume, &muted);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // In B2G, the system app manages audio playback policy. If there is a new
+    // sound want to be playback, it must wait for the permission from the
+    // system app. It means that the sound would be muted first, and then be
+    // unmuted. For telephony, the behaviors are hold() first, then resume().
+    // However, the telephony service can't handle all these requests within a
+    // short period. The telephony service would reject our resume request,
+    // because the modem have not changed the call state yet. It causes that
+    // the telephony can't be resumed. Therefore, we don't mute the telephony
+    // at the beginning.
+    volume = 1.0;
+    muted = false;
+    rv = WindowVolumeChanged(volume, muted);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+  return NS_OK;
+}
+
 bool
 Telephony::GetMuted(ErrorResult& aRv) const
 {
   bool muted = false;
   aRv = mService->GetMicrophoneMuted(&muted);
 
   return muted;
 }
@@ -586,23 +658,87 @@ Telephony::GetReady(ErrorResult& aRv) co
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = mReadyPromise;
   return promise.forget();
 }
 
+// nsIAudioChannelAgentCallback
+
+NS_IMETHODIMP
+Telephony::WindowVolumeChanged(float aVolume, bool aMuted)
+{
+  // It's impossible to put all the calls on-hold in the multi-call case.
+  if (mCalls.Length() > 1 ||
+     (mCalls.Length() == 1 && mGroup->CallsArray().Length())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult rv;
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  nsRefPtr<Promise> promise = Promise::Create(global, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
+  bool isSingleCall = mCalls.Length();
+  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
+  if (isSingleCall) {
+    rv = aMuted ? mCalls[0]->Hold(callback) : mCalls[0]->Resume(callback);
+  } else {
+    rv = aMuted ? mGroup->Hold(callback) : mGroup->Resume(callback);
+  }
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
+  // These events will be triggered when the telephony is interrupted by other
+  // audio channel.
+  if (mMuted != aMuted) {
+    mMuted = aMuted;
+    // We should not dispatch "mozinterruptend" when the system app initializes
+    // the telephony audio from muted to unmuted at the first time. The event
+    // "mozinterruptend" must be dispatched after the "mozinterruptbegin".
+    if (!mHaveDispatchedInterruptBeginEvent && mMuted) {
+      DispatchTrustedEvent(NS_LITERAL_STRING("mozinterruptbegin"));
+      mHaveDispatchedInterruptBeginEvent = mMuted;
+    } else if (mHaveDispatchedInterruptBeginEvent && !mMuted) {
+      DispatchTrustedEvent(NS_LITERAL_STRING("mozinterruptend"));
+      mHaveDispatchedInterruptBeginEvent = mMuted;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Telephony::WindowAudioCaptureChanged()
+{
+  // Do nothing, it's useless for the telephony object.
+  return NS_OK;
+}
+
 // nsITelephonyListener
 
 NS_IMETHODIMP
 Telephony::CallStateChanged(uint32_t aLength, nsITelephonyCallInfo** aAllInfo)
 {
+  nsresult rv;
   for (uint32_t i = 0; i < aLength; ++i) {
-    HandleCallInfo(aAllInfo[i]);
+    rv = HandleCallInfo(aAllInfo[i]);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  rv = HandleAudioAgentState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::EnumerateCallState(nsITelephonyCallInfo* aInfo)
 {
   return HandleCallInfo(aInfo);
@@ -628,16 +764,17 @@ Telephony::EnumerateCallStateComplete()
         callState = nsITelephonyService::CALL_STATE_UNKNOWN;
         break;
       }
     }
 
     mGroup->ChangeState(callState);
   }
 
+  HandleAudioAgentState();
   if (mReadyPromise) {
     mReadyPromise->MaybeResolve(JS::UndefinedHandleValue);
   }
 
   if (NS_FAILED(mService->RegisterListener(mListener))) {
     NS_WARNING("Failed to register listener!");
   }
   return NS_OK;
--- a/dom/telephony/Telephony.h
+++ b/dom/telephony/Telephony.h
@@ -2,16 +2,18 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_telephony_telephony_h__
 #define mozilla_dom_telephony_telephony_h__
 
+#include "AudioChannelService.h"
+
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/telephony/TelephonyCommon.h"
 
 #include "nsITelephonyCallInfo.h"
 #include "nsITelephonyService.h"
 
 // Need to include TelephonyCall.h because we have inline methods that
@@ -26,41 +28,50 @@ namespace telephony {
 
 class TelephonyDialCallback;
 
 } // namespace telephony
 
 class OwningTelephonyCallOrTelephonyCallGroup;
 
 class Telephony final : public DOMEventTargetHelper,
+                        public nsIAudioChannelAgentCallback,
                         private nsITelephonyListener
 {
   /**
    * Class Telephony doesn't actually expose nsITelephonyListener.
    * Instead, it owns an nsITelephonyListener derived instance mListener
    * and passes it to nsITelephonyService. The onreceived events are first
    * delivered to mListener and then forwarded to its owner, Telephony. See
    * also bug 775997 comment #51.
    */
   class Listener;
 
   friend class telephony::TelephonyDialCallback;
 
+  // The audio agent is needed to communicate with the audio channel service.
+  nsCOMPtr<nsIAudioChannelAgent> mAudioAgent;
   nsCOMPtr<nsITelephonyService> mService;
   nsRefPtr<Listener> mListener;
 
   nsTArray<nsRefPtr<TelephonyCall> > mCalls;
   nsRefPtr<CallsList> mCallsList;
 
   nsRefPtr<TelephonyCallGroup> mGroup;
 
   nsRefPtr<Promise> mReadyPromise;
 
+  uint32_t mAudioAgentNotify;
+  bool mIsAudioStartPlaying;
+  bool mHaveDispatchedInterruptBeginEvent;
+  bool mMuted;
+
 public:
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
   NS_DECL_NSITELEPHONYLISTENER
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Telephony,
                                            DOMEventTargetHelper)
 
   nsPIDOMWindow*
   GetParentObject() const
   {
@@ -89,16 +100,25 @@ public:
 
   void
   StartTone(const nsAString& aDTMFChar, const Optional<uint32_t>& aServiceId,
             ErrorResult& aRv);
 
   void
   StopTone(const Optional<uint32_t>& aServiceId, ErrorResult& aRv);
 
+  // In the audio channel architecture, the system app needs to know the state
+  // of every audio channel, including the telephony. Therefore, when a
+  // telephony call is activated , the audio channel service would notify the
+  // system app about that. And we need an agent to communicate with the audio
+  // channel service. We would follow the call states to make a correct
+  // notification.
+  void
+  OwnAudioChannel(ErrorResult& aRv);
+
   bool
   GetMuted(ErrorResult& aRv) const;
 
   void
   SetMuted(bool aMuted, ErrorResult& aRv);
 
   bool
   GetSpeakerEnabled(ErrorResult& aRv) const;
@@ -208,14 +228,18 @@ private:
   already_AddRefed<TelephonyCall>
   GetCall(uint32_t aServiceId, uint32_t aCallIndex);
 
   already_AddRefed<TelephonyCall>
   GetCallFromEverywhere(uint32_t aServiceId, uint32_t aCallIndex);
 
   nsresult
   HandleCallInfo(nsITelephonyCallInfo* aInfo);
+
+  // Check the call states to decide whether need to send the notificaiton.
+  nsresult
+  HandleAudioAgentState();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_telephony_telephony_h__
--- a/dom/telephony/TelephonyCall.cpp
+++ b/dom/telephony/TelephonyCall.cpp
@@ -319,74 +319,101 @@ TelephonyCall::HangUp(ErrorResult& aRv)
 already_AddRefed<Promise>
 TelephonyCall::Hold(ErrorResult& aRv)
 {
   nsRefPtr<Promise> promise = CreatePromise(aRv);
   if (!promise) {
     return nullptr;
   }
 
-  if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) {
-    NS_WARNING(nsPrintfCString("Hold non-connected call is rejected!"
-                               " (State: %u)", mCallState).get());
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
-  }
-
-  if (mGroup) {
-    NS_WARNING("Hold a call in conference is rejected!");
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
-  }
-
-  if (!mSwitchable) {
-    NS_WARNING("Hold a non-switchable call is rejected!");
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
-  }
-
   nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
-  aRv = mTelephony->Service()->HoldCall(mServiceId, mCallIndex, callback);
-  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
-
-  if (mSecondId) {
-    // No state transition when we switch two numbers within one TelephonyCall
-    // object. Otherwise, the state here will be inconsistent with the backend
-    // RIL and will never be right.
-    return promise.forget();
+  aRv = Hold(callback);
+  if (NS_WARN_IF(aRv.Failed() &&
+                 !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) {
+    return nullptr;
   }
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 TelephonyCall::Resume(ErrorResult& aRv)
 {
   nsRefPtr<Promise> promise = CreatePromise(aRv);
   if (!promise) {
     return nullptr;
   }
 
-  if (mCallState != nsITelephonyService::CALL_STATE_HELD) {
-    NS_WARNING(nsPrintfCString("Resume non-held call is rejected!"
+  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
+  aRv = Resume(callback);
+  if (NS_WARN_IF(aRv.Failed() &&
+                 !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) {
+    return nullptr;
+  }
+
+  return promise.forget();
+}
+
+nsresult
+TelephonyCall::Hold(nsITelephonyCallback* aCallback)
+{
+  if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) {
+    NS_WARNING(nsPrintfCString("Hold non-connected call is rejected!"
                                " (State: %u)", mCallState).get());
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (mGroup) {
+    NS_WARNING("Hold a call in conference is rejected!");
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (!mSwitchable) {
+    NS_WARNING("Hold a non-switchable call is rejected!");
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  nsresult rv = mTelephony->Service()->HoldCall(mServiceId, mCallIndex, aCallback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mSecondId) {
+    // No state transition when we switch two numbers within one TelephonyCall
+    // object. Otherwise, the state here will be inconsistent with the backend
+    // RIL and will never be right.
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+TelephonyCall::Resume(nsITelephonyCallback* aCallback)
+{
+  if (mCallState != nsITelephonyService::CALL_STATE_HELD) {
+    NS_WARNING("Resume non-held call is rejected!");
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   if (mGroup) {
     NS_WARNING("Resume a call in conference is rejected!");
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   if (!mSwitchable) {
     NS_WARNING("Resume a non-switchable call is rejected!");
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
-  aRv = mTelephony->Service()->ResumeCall(mServiceId, mCallIndex, callback);
-  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+  nsresult rv = mTelephony->Service()->ResumeCall(mServiceId, mCallIndex, aCallback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
 
-  return promise.forget();
-}
+  return NS_OK;
+}
\ No newline at end of file
--- a/dom/telephony/TelephonyCall.h
+++ b/dom/telephony/TelephonyCall.h
@@ -8,16 +8,18 @@
 #define mozilla_dom_telephony_telephonycall_h__
 
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TelephonyCallBinding.h"
 #include "mozilla/dom/TelephonyCallId.h"
 #include "mozilla/dom/telephony/TelephonyCommon.h"
 
+#include "nsITelephonyService.h"
+
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class TelephonyCall final : public DOMEventTargetHelper
 {
   nsRefPtr<Telephony> mTelephony;
@@ -180,16 +182,22 @@ public:
   void
   ChangeGroup(TelephonyCallGroup* aGroup);
 
 private:
   explicit TelephonyCall(nsPIDOMWindow* aOwner);
 
   ~TelephonyCall();
 
+  nsresult
+  Hold(nsITelephonyCallback* aCallback);
+
+  nsresult
+  Resume(nsITelephonyCallback* aCallback);
+
   void
   ChangeStateInternal(uint16_t aCallState, bool aFireEvents);
 
   nsresult
   DispatchCallEvent(const nsAString& aType,
                     TelephonyCall* aCall);
 
   already_AddRefed<Promise>
--- a/dom/telephony/TelephonyCallGroup.cpp
+++ b/dom/telephony/TelephonyCallGroup.cpp
@@ -342,43 +342,73 @@ TelephonyCallGroup::Hold(ErrorResult& aR
 {
   MOZ_ASSERT(!mCalls.IsEmpty());
 
   nsRefPtr<Promise> promise = CreatePromise(aRv);
   if (!promise) {
     return nullptr;
   }
 
-  if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) {
-    NS_WARNING("Holding a non-connected call is rejected!");
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
+  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
+  aRv = Hold(callback);
+  if (NS_WARN_IF(aRv.Failed() &&
+                 !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) {
+    return nullptr;
   }
 
-  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
-  aRv = mTelephony->Service()->HoldConference(mCalls[0]->ServiceId(),
-                                              callback);
-  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 TelephonyCallGroup::Resume(ErrorResult& aRv)
 {
   MOZ_ASSERT(!mCalls.IsEmpty());
 
   nsRefPtr<Promise> promise = CreatePromise(aRv);
   if (!promise) {
     return nullptr;
   }
 
-  if (mCallState != nsITelephonyService::CALL_STATE_HELD) {
-    NS_WARNING("Resuming a non-held call is rejected!");
-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return promise.forget();
+  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
+  aRv = Resume(callback);
+  if (NS_WARN_IF(aRv.Failed() &&
+                 !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR))) {
+    return nullptr;
+  }
+
+  return promise.forget();
+}
+
+nsresult
+TelephonyCallGroup::Hold(nsITelephonyCallback* aCallback)
+{
+  if (mCallState != nsITelephonyService::CALL_STATE_CONNECTED) {
+    NS_WARNING("Holding a non-connected call is rejected!");
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  nsCOMPtr<nsITelephonyCallback> callback = new TelephonyCallback(promise);
-  aRv = mTelephony->Service()->ResumeConference(mCalls[0]->ServiceId(),
-                                                callback);
-  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
-  return promise.forget();
+  nsresult rv = mTelephony->Service()->HoldConference(mCalls[0]->ServiceId(),
+                                                      aCallback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
 }
+
+nsresult
+TelephonyCallGroup::Resume(nsITelephonyCallback* aCallback)
+{
+  if (mCallState != nsITelephonyService::CALL_STATE_HELD) {
+    NS_WARNING("Resuming a non-held call is rejected!");
+    aCallback->NotifyError(NS_LITERAL_STRING("InvalidStateError"));
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  nsresult rv = mTelephony->Service()->ResumeConference(mCalls[0]->ServiceId(),
+                                                        aCallback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
--- a/dom/telephony/TelephonyCallGroup.h
+++ b/dom/telephony/TelephonyCallGroup.h
@@ -25,16 +25,18 @@ class TelephonyCallGroup final : public 
 
   uint16_t mCallState;
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCallGroup,
                                            DOMEventTargetHelper)
 
+  friend class Telephony;
+
   nsPIDOMWindow*
   GetParentObject() const
   {
     return GetOwner();
   }
 
   // WrapperCache
   virtual JSObject*
@@ -104,16 +106,22 @@ public:
   nsresult
   NotifyError(const nsAString& aName, const nsAString& aMessage);
 
 private:
   explicit TelephonyCallGroup(nsPIDOMWindow* aOwner);
   ~TelephonyCallGroup();
 
   nsresult
+  Hold(nsITelephonyCallback* aCallback);
+
+  nsresult
+  Resume(nsITelephonyCallback* aCallback);
+
+  nsresult
   NotifyCallsChanged(TelephonyCall* aCall);
 
   nsresult
   DispatchCallEvent(const nsAString& aType,
                     TelephonyCall* aCall);
 
   already_AddRefed<Promise>
   CreatePromise(ErrorResult& aRv);
--- a/dom/webidl/Telephony.webidl
+++ b/dom/webidl/Telephony.webidl
@@ -44,16 +44,22 @@ interface Telephony : EventTarget {
   Promise<void> sendTones(DOMString tones, optional unsigned long pauseDuration = 3000, optional unsigned long toneDuration = 70, optional unsigned long serviceId);
 
   [Throws]
   void startTone(DOMString tone, optional unsigned long serviceId);
 
   [Throws]
   void stopTone(optional unsigned long serviceId);
 
+  // Calling this method, the app will be treated as owner of the telephony
+  // calls from the AudioChannel policy.
+  [Throws,
+   CheckAllPermissions="audio-channel-telephony"]
+  void ownAudioChannel();
+
   [Throws]
   attribute boolean muted;
 
   [Throws]
   attribute boolean speakerEnabled;
 
   readonly attribute (TelephonyCall or TelephonyCallGroup)? active;
 
--- a/dom/webidl/ThreadSafeChromeUtils.webidl
+++ b/dom/webidl/ThreadSafeChromeUtils.webidl
@@ -9,23 +9,25 @@
  * to Chrome. This interface is exposed in workers, while ChromeUtils is not.
  */
 [ChromeOnly, Exposed=(Window,System,Worker)]
 interface ThreadSafeChromeUtils {
   /**
    * Serialize a snapshot of the heap graph, as seen by |JS::ubi::Node| and
    * restricted by |boundaries|, and write it to the provided file path.
    *
-   * @param filePath          The file path to write the heap snapshot to.
+   * @param boundaries        The portion of the heap graph to write.
    *
-   * @param boundaries        The portion of the heap graph to write.
+   * @returns                 The path to the file the heap snapshot was written
+   *                          to. This is guaranteed to be within the temp
+   *                          directory and its file name will match the regexp
+   *                          `\d+(\-\d+)?\.fxsnapshot`.
    */
   [Throws]
-  static void saveHeapSnapshot(DOMString filePath,
-                               optional HeapSnapshotBoundaries boundaries);
+  static DOMString saveHeapSnapshot(optional HeapSnapshotBoundaries boundaries);
 
   /**
    * Deserialize a core dump into a HeapSnapshot.
    *
    * @param filePath          The file path to read the heap snapshot from.
    */
   [Throws, NewObject]
   static HeapSnapshot readHeapSnapshot(DOMString filePath);
--- a/dom/wifi/test/marionette/manifest.ini
+++ b/dom/wifi/test/marionette/manifest.ini
@@ -5,18 +5,21 @@ qemu = true
 
 [test_wifi_enable.js]
 [test_wifi_scan.js]
 [test_wifi_associate.js]
 [test_wifi_associate_wo_connect.js]
 [test_wifi_auto_connect.js]
 [test_wifi_static_ip.js]
 [test_wifi_tethering_wifi_disabled.js]
+skip-if = android_version > '15' # Bug 1203075
 [test_wifi_tethering_wifi_inactive.js]
+skip-if = android_version > '15' # Bug 1203075
 [test_wifi_tethering_wifi_active.js]
+skip-if = android_version > '15' # Bug 1203075
 [test_wifi_manage_server_certificate.js]
 [test_wifi_manage_user_certificate.js]
 [test_wifi_manage_pkcs12_certificate.js]
 [test_wifi_associate_WPA_EAP_PEAP.js]
 disabled = Bug 1173697
 [test_wifi_associate_WPA_EAP_TTLS.js]
 disabled = Bug 1173697
 [test_wifi_associate_WPA_EAP_TLS.js]
--- a/mobile/android/.eslintignore
+++ b/mobile/android/.eslintignore
@@ -16,9 +16,8 @@ locales/
 modules/ContactService.jsm
 
 # es7 proposed: array comprehensions
 #   https://github.com/eslint/espree/issues/125
 modules/WebappManager.jsm
 
 # Non-standard `(catch ex if ...)`
 components/Snippets.js
-modules/MatchstickApp.jsm
--- a/mobile/android/b2gdroid/app/Makefile.in
+++ b/mobile/android/b2gdroid/app/Makefile.in
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml
 
 JAVAFILES := \
+  src/main/java/org/mozilla/b2gdroid/Apps.java \
   src/main/java/org/mozilla/b2gdroid/Launcher.java \
   src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
   $(NULL)
 
 # The GeckoView consuming APK depends on the GeckoView JAR files.  There are two
 # issues: first, the GeckoView JAR files need to be built before they are
 # consumed here.  This happens for delicate reasons.  In the (serial) libs tier,
 # base/ is traversed before b2gdroid/app.  Since base/libs builds classes.dex,
new file mode 100644
--- /dev/null
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.b2gdroid;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Iterator;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import android.net.Uri;
+import android.util.Base64;
+import android.util.Log;
+
+import org.json.JSONObject;
+import org.json.JSONArray;
+
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.util.GeckoEventListener;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+
+class Apps extends BroadcastReceiver
+           implements GeckoEventListener {
+    private static final String LOGTAG = "B2G:Apps";
+
+    private Context mContext;
+
+    Apps(Context context) {
+        mContext = context;
+        EventDispatcher.getInstance()
+                       .registerGeckoThreadListener(this,
+                                                    "Apps:GetList",
+                                                    "Apps:Launch",
+                                                    "Apps:Uninstall");
+
+        // Observe app installation and removal.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(this, filter);
+    }
+
+    void destroy() {
+        mContext.unregisterReceiver(this);
+        EventDispatcher.getInstance()
+                       .unregisterGeckoThreadListener(this,
+                                                      "Apps:GetList",
+                                                      "Apps:Launch",
+                                                      "Apps:Uninstall");
+    }
+
+    JSONObject activityInfoToJson(ActivityInfo info, PackageManager pm) {
+        JSONObject obj = new JSONObject();
+        try {
+            obj.put("name", info.loadLabel(pm).toString());
+            obj.put("packagename", info.packageName);
+            obj.put("classname", info.name);
+
+            final ApplicationInfo appInfo = info.applicationInfo;
+            // Pre-installed apps can't be uninstalled.
+            final boolean removable =
+                (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0;
+
+            obj.put("removable", removable);
+
+            // For now, create a data: url for the icon, since we need additional
+            // android:// protocol support for icons. Once it's there we'll do
+            // something like: obj.put("icon", "android:icon/" + info.packageName);
+            Drawable d = pm.getApplicationIcon(info.packageName);
+            Bitmap bitmap = ((BitmapDrawable)d).getBitmap();
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
+            byte[] byteArray = byteArrayOutputStream.toByteArray();
+            String encoded = Base64.encodeToString(byteArray, Base64.DEFAULT);
+            obj.put("icon", "data:image/png;base64," + encoded);
+        } catch(Exception ex) {
+            Log.wtf(LOGTAG, "Error building ActivityInfo JSON", ex);
+        }
+        return obj;
+    }
+
+    public void handleMessage(String event, JSONObject message) {
+        Log.w(LOGTAG, "Received " + event);
+
+        if ("Apps:GetList".equals(event)) {
+            JSONObject ret = new JSONObject();
+            JSONArray array = new JSONArray();
+            PackageManager pm = mContext.getPackageManager();
+            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            final Iterator<ResolveInfo> i = pm.queryIntentActivities(mainIntent, 0).iterator();
+            try {
+                while (i.hasNext()) {
+                    ActivityInfo info = i.next().activityInfo;
+                    array.put(activityInfoToJson(info, pm));
+                }
+                ret.put("apps", array);
+            } catch(Exception ex) {
+                Log.wtf(LOGTAG, "error, making list of apps", ex);
+            }
+            EventDispatcher.sendResponse(message, ret);
+        } else if ("Apps:Launch".equals(event)) {
+            try {
+                String className = message.getString("classname");
+                String packageName = message.getString("packagename");
+                final Intent intent = new Intent(Intent.ACTION_MAIN, null);
+                intent.addCategory(Intent.CATEGORY_LAUNCHER);
+                intent.setClassName(packageName, className);
+                mContext.startActivity(intent);
+            } catch(Exception ex) {
+                Log.wtf(LOGTAG, "Error launching app", ex);
+            }
+        } else if ("Apps:Uninstall".equals(event)) {
+            try {
+                String packageName = message.getString("packagename");
+                Uri packageUri = Uri.parse("package:" + packageName);
+                final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
+                mContext.startActivity(intent);
+            } catch(Exception ex) {
+                Log.wtf(LOGTAG, "Error uninstalling app", ex);
+            }
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(LOGTAG, intent.getAction() + " " + intent.getDataString());
+
+        String packageName = intent.getDataString().substring(8);
+        String action = intent.getAction();
+        if ("android.intent.action.PACKAGE_ADDED".equals(action)) {
+            PackageManager pm = mContext.getPackageManager();
+            Intent launch = pm.getLaunchIntentForPackage(packageName);
+            if (launch == null) {
+                Log.d(LOGTAG, "No launchable intent for " + packageName);
+                return;
+            }
+            ActivityInfo info = launch.resolveActivityInfo(pm, 0);
+
+            JSONObject obj = activityInfoToJson(info, pm);
+            GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Apps:Installed", obj.toString());
+            GeckoAppShell.sendEventToGecko(e);
+        } else if ("android.intent.action.PACKAGE_REMOVED".equals(action)) {
+            JSONObject obj = new JSONObject();
+            try {
+                obj.put("packagename", packageName);
+            } catch(Exception ex) {
+                Log.wtf(LOGTAG, "Error building PACKAGE_REMOVED JSON", ex);
+            }
+            GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Apps:Uninstalled", obj.toString());
+            GeckoAppShell.sendEventToGecko(e);
+        }
+    }
+}
--- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
@@ -1,78 +1,66 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.b2gdroid;
 
-import java.io.ByteArrayOutputStream;
-import java.util.Iterator;
-
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.KeyguardManager.KeyguardLock;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.os.Bundle;
-import android.util.Base64;
 import android.util.Log;
 import android.view.View;
-import android.widget.ImageView;
 
 import org.json.JSONObject;
-import org.json.JSONArray;
-import org.json.JSONException;
 
 import org.mozilla.gecko.BaseGeckoInterface;
 import org.mozilla.gecko.ContactService;
 import org.mozilla.gecko.ContextGetter;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoBatteryManager;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.mozilla.b2gdroid.ScreenStateObserver;
+import org.mozilla.b2gdroid.Apps;
 
 public class Launcher extends Activity
                       implements GeckoEventListener, ContextGetter {
     private static final String LOGTAG = "B2G";
 
     private ContactService      mContactService;
     private ScreenStateObserver mScreenStateObserver;
+    private Apps                mApps;
 
     /** ContextGetter */
     public Context getContext() {
         return this;
     }
 
     public SharedPreferences getSharedPreferences() {
         return null;
     }
 
     /** Initializes Gecko APIs */
     private void initGecko() {
         GeckoAppShell.setContextGetter(this);
 
         GeckoBatteryManager.getInstance().start(this);
         mContactService = new ContactService(EventDispatcher.getInstance(), this);
+        mApps = new Apps(this);
     }
 
     private void hideSplashScreen() {
         final View splash = findViewById(R.id.splashscreen);
         runOnUiThread(new Runnable() {
             @Override public void run() {
                 splash.setVisibility(View.GONE);
             }
@@ -118,16 +106,17 @@ public class Launcher extends Activity
         IntentHelper.destroy();
         mScreenStateObserver.destroy(this);
         mScreenStateObserver = null;
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "Launcher:Ready");
 
         mContactService.destroy();
+        mApps.destroy();
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         final String action = intent.getAction();
         Log.w(LOGTAG, "onNewIntent " + action);
         if (Intent.ACTION_VIEW.equals(action)) {
             Log.w(LOGTAG, "Asking gecko to view " + intent.getDataString());
deleted file mode 100644
index ec0857ffd21e24211d02e16eb5ef347d1d56c3b8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index b944b3351d42ee5c29c56964325b152f2dcc8969..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/base/resources/values-v11/styles.xml
+++ b/mobile/android/base/resources/values-v11/styles.xml
@@ -93,19 +93,22 @@
     <style name="GeckoActionBar.Button" parent="android:style/Widget.Holo.Light.ActionButton">
         <item name="android:padding">8dip</item>
         <!-- The default implementation doesn't do any image scaling. Our custom menus mean we can't just use the same image
              in both menus and the actionbar without doing some scaling though. -->
         <item name="android:scaleType">centerInside</item>
     </style>
 
     <style name="GeckoActionBar.Button.MenuButton" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
-        <item name="android:scaleType">center</item>
+        <item name="android:scaleType">centerInside</item>
         <item name="android:background">@android:color/transparent</item>
-        <item name="android:src">@drawable/menu_light</item>
+        <item name="android:src">@drawable/menu</item>
+        <item name="android:tint">@color/toolbar_icon_grey</item>
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:layout_marginBottom">16dp</item>
     </style>
 
     <style name="TabInput"></style>
 
     <style name="TabInput.TabWidget" parent="android:style/Widget.Holo.Light.TabWidget"/>
 
     <style name="TabInput.Tab" parent="android:style/Widget.Holo.Light.Tab">
         <item name="android:minHeight">@dimen/menu_item_row_height</item>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -725,17 +725,18 @@
     </style>
 
     <style name="GeckoActionBar.Button" parent="Widget.MenuItemActionBar">
         <item name="android:padding">8dp</item>
     </style>
 
     <style name="GeckoActionBar.Button.MenuButton">
         <item name="android:scaleType">center</item>
-        <item name="android:src">@drawable/menu_light</item>
+        <item name="android:src">@drawable/menu</item>
+        <item name="android:tint">@color/toolbar_icon_grey</item>
         <item name="android:contentDescription">@string/actionbar_menu</item>
         <item name="android:background">@android:color/transparent</item>
     </style>
 
     <style name="GeckoActionBar.Buttons">
         <item name="android:background">@android:color/transparent</item>
         <item name="android:textColor">@color/placeholder_active_grey</item>
         <item name="android:gravity">right</item>
--- a/mobile/android/chrome/content/.eslintrc
+++ b/mobile/android/chrome/content/.eslintrc
@@ -5,17 +5,16 @@ globals:
     Ci: false
     Cu: false
     NativeWindow: false
     PageActions: false
     ReaderMode: false
     SimpleServiceDiscovery: false
     TabMirror: false
     MediaPlayerApp: false
-    MatchstickApp: false
     RokuApp: false
     SearchEngines: false
     ConsoleAPI: true
     Point: false
     Rect: false
 
 rules:
     # Disabled stuff
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -15,30 +15,16 @@ var rokuDevice = {
   factory: function(aService) {
     Cu.import("resource://gre/modules/RokuApp.jsm");
     return new RokuApp(aService);
   },
   types: ["video/mp4"],
   extensions: ["mp4"]
 };
 
-var matchstickDevice = {
-  id: "matchstick:dial",
-  target: "urn:dial-multiscreen-org:service:dial:1",
-  filters: {
-    manufacturer: "openflint"
-  },
-  factory: function(aService) {
-    Cu.import("resource://gre/modules/MatchstickApp.jsm");
-    return new MatchstickApp(aService);
-  },
-  types: ["video/mp4", "video/webm"],
-  extensions: ["mp4", "webm"]
-};
-
 var mediaPlayerDevice = {
   id: "media:router",
   target: "media:router",
   factory: function(aService) {
     Cu.import("resource://gre/modules/MediaPlayerApp.jsm");
     return new MediaPlayerApp(aService);
   },
   types: ["video/mp4", "video/webm", "application/x-mpegurl"],
@@ -80,17 +66,16 @@ var CastingApps = {
 
   init: function ca_init() {
     if (!this.isCastingEnabled()) {
       return;
     }
 
     // Register targets
     SimpleServiceDiscovery.registerDevice(rokuDevice);
-    SimpleServiceDiscovery.registerDevice(matchstickDevice);
 
     // MediaPlayerDevice will notify us any time the native device list changes.
     mediaPlayerDevice.init();
     SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
 
     // Search for devices continuously every 120 seconds
     SimpleServiceDiscovery.search(120 * 1000);
 
deleted file mode 100644
--- a/mobile/android/modules/MatchstickApp.jsm
+++ /dev/null
@@ -1,375 +0,0 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["MatchstickApp"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-function log(msg) {
-  Services.console.logStringMessage(msg);
-}
-
-const MATCHSTICK_PLAYER_URL = "http://openflint.github.io/flint-player/player.html";
-
-const STATUS_RETRY_COUNT = 5;   // Number of times we retry a partial status
-const STATUS_RETRY_WAIT = 1000; // Delay between attempts in milliseconds
-
-/* MatchstickApp is a wrapper for interacting with a DIAL server.
- * The basic interactions all use a REST API.
- * See: https://github.com/openflint/openflint.github.io/wiki/Flint%20Protocol%20Docs
- */
-function MatchstickApp(aServer) {
-  this.server = aServer;
-  this.app = "~flintplayer";
-  this.resourceURL = this.server.appsURL + this.app;
-  this.token = null;
-  this.statusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-  this.statusRetry = 0;
-}
-
-MatchstickApp.prototype = {
-  status: function status(aCallback) {
-    // Query the server to see if an application is already running
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("GET", this.resourceURL, true);
-    xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
-    xhr.setRequestHeader("Accept", "application/xml; charset=utf8");
-    xhr.setRequestHeader("Authorization", this.token);
-
-    xhr.addEventListener("load", (function() {
-      if (xhr.status == 200) {
-        let doc = xhr.responseXML;
-        let state = doc.querySelector("state").textContent;
-
-        // The serviceURL can be missing if the player is not completely loaded
-        let serviceURL = null;
-        let serviceNode = doc.querySelector("channelBaseUrl");
-        if (serviceNode) {
-          serviceURL = serviceNode.textContent + "/senders/" + this.token;
-        }
-
-        if (aCallback)
-          aCallback({ state: state, serviceURL: serviceURL });
-      } else {
-        if (aCallback)
-          aCallback({ state: "error" });
-      }
-    }).bind(this), false);
-
-    xhr.addEventListener("error", (function() {
-      if (aCallback)
-        aCallback({ state: "error" });
-    }).bind(this), false);
-
-    xhr.send(null);
-  },
-
-  start: function start(aCallback) {
-    // Start a given app with any extra query data. Each app uses it's own data scheme.
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("POST", this.resourceURL, true);
-    xhr.overrideMimeType("text/xml");
-    xhr.setRequestHeader("Content-Type", "application/json");
-
-    xhr.addEventListener("load", (function() {
-      if (xhr.status == 200 || xhr.status == 201) {
-        this.statusRetry = 0;
-
-        let response = JSON.parse(xhr.responseText);
-        this.token = response.token;
-        this.pingInterval = response.interval;
-
-        if (aCallback)
-          aCallback(true);
-      } else {
-        if (aCallback)
-          aCallback(false);
-      }
-    }).bind(this), false);
-
-    xhr.addEventListener("error", (function() {
-      if (aCallback)
-        aCallback(false);
-    }).bind(this), false);
-
-    let data = {
-      type: "launch",
-      app_info: {
-        url: MATCHSTICK_PLAYER_URL,
-        useIpc: true,
-        maxInactive: -1
-      }
-    };
-
-    xhr.send(JSON.stringify(data));
-  },
-
-  stop: function stop(aCallback) {
-    // Send command to kill an app, if it's already running.
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("DELETE", this.resourceURL + "/run", true);
-    xhr.overrideMimeType("text/plain");
-    xhr.setRequestHeader("Accept", "application/xml; charset=utf8");
-    xhr.setRequestHeader("Authorization", this.token);
-
-    xhr.addEventListener("load", (function() {
-      if (xhr.status == 200) {
-        if (aCallback)
-          aCallback(true);
-      } else {
-        if (aCallback)
-          aCallback(false);
-      }
-    }).bind(this), false);
-
-    xhr.addEventListener("error", (function() {
-      if (aCallback)
-        aCallback(false);
-    }).bind(this), false);
-
-    xhr.send(null);
-  },
-
-  remoteMedia: function remoteMedia(aCallback, aListener) {
-    this.status((aStatus) => {
-      if (aStatus.serviceURL) {
-        if (aCallback) {
-          aCallback(new RemoteMedia(aStatus.serviceURL, aListener, this));
-        }
-        return;
-      }
-
-      // It can take a few moments for the player app to load. Let's use a small delay
-      // and retry a few times.
-      if (this.statusRetry < STATUS_RETRY_COUNT) {
-        this.statusRetry++;
-        this.statusTimer.initWithCallback(() => {
-          this.remoteMedia(aCallback, aListener);
-        }, STATUS_RETRY_WAIT, Ci.nsITimer.TYPE_ONE_SHOT);
-      } else {
-        // Fail
-        if (aCallback) {
-          aCallback();
-        }
-      }
-    });
-  }
-}
-
-/* RemoteMedia provides a wrapper for using WebSockets and Flint protocol to control
- * the Matchstick media player
- * See: https://github.com/openflint/openflint.github.io/wiki/Flint%20Protocol%20Docs
- * See: https://github.com/openflint/flint-receiver-sdk/blob/gh-pages/v1/libs/mediaplayer.js
- */
-function RemoteMedia(aURL, aListener, aApp) {
-  this._active = false;
-  this._status = "uninitialized";
-
-  this.app = aApp;
-  this.listener = aListener;
-
-  this.pingTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
-  let uri = Services.io.newURI(aURL, null, null);
-  this.ws = Cc["@mozilla.org/network/protocol;1?name=ws"].createInstance(Ci.nsIWebSocketChannel);
-  this.ws.initLoadInfo(null, // aLoadingNode
-                       Services.scriptSecurityManager.getSystemPrincipal(),
-                       null, // aTriggeringPrincipal
-                       Ci.nsILoadInfo.SEC_NORMAL,
-                       Ci.nsIContentPolicy.TYPE_WEBSOCKET);
-
-  this.ws.asyncOpen(uri, aURL, this, null);
-}
-
-// Used to give us a small gap between not pinging too often and pinging too late
-const PING_INTERVAL_BACKOFF = 200;
-
-RemoteMedia.prototype = {
-  _ping: function _ping() {
-    if (this.app.pingInterval == -1) {
-      return;
-    }
-
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-    xhr.open("GET", this.app.resourceURL, true);
-    xhr.setRequestHeader("Accept", "application/xml; charset=utf8");
-    xhr.setRequestHeader("Authorization", this.app.token);
-
-    xhr.addEventListener("load", () => {
-      if (xhr.status == 200) {
-        this.pingTimer.initWithCallback(() => {
-          this._ping();
-        }, this.app.pingInterval - PING_INTERVAL_BACKOFF, Ci.nsITimer.TYPE_ONE_SHOT);
-      }
-    });
-
-    xhr.send(null);
-  },
-
-  _changeStatus: function _changeStatus(status) {
-    if (this._status != status) {
-      this._status = status;
-      if ("onRemoteMediaStatus" in this.listener) {
-        this.listener.onRemoteMediaStatus(this);
-      }
-    }
-  },
-
-  _teardown: function _teardown() {
-    if (!this._active) {
-      return;
-    }
-
-    // Stop any queued ping event
-    this.pingTimer.cancel();
-
-    // Let the listener know we are finished
-    this._active = false;
-    if (this.listener && "onRemoteMediaStop" in this.listener) {
-      this.listener.onRemoteMediaStop(this);
-    }
-  },
-
-  _sendMsg: function _sendMsg(params) {
-    // Convert payload to a string
-    params.payload = JSON.stringify(params.payload);
-
-    try {
-      this.ws.sendMsg(JSON.stringify(params));
-    } catch (e if e.result == Cr.NS_ERROR_NOT_CONNECTED) {
-      // This shouldn't happen unless something gets out of sync with the
-      // connection. Let's make sure we try to cleanup.
-      this._teardown();
-    } catch (e) {
-      log("Send Error: " + e)
-    }
-  },
-
-  get active() {
-    return this._active;
-  },
-
-  get status() {
-    return this._status;
-  },
-
-  shutdown: function shutdown() {
-    this.ws.close(Ci.nsIWebSocketChannel.CLOSE_NORMAL, "shutdown");
-  },
-
-  play: function play() {
-    if (!this._active) {
-      return;
-    }
-
-    let params = {
-      namespace: "urn:flint:org.openflint.fling.media",
-      payload: {
-        type: "PLAY",
-        requestId: "requestId-5",
-      }
-    };
-
-    this._sendMsg(params);
-  },
-
-  pause: function pause() {
-    if (!this._active) {
-      return;
-    }
-
-    let params = {
-      namespace: "urn:flint:org.openflint.fling.media",
-      payload: {
-        type: "PAUSE",
-        requestId: "requestId-4",
-      }
-    };
-
-    this._sendMsg(params);
-  },
-
-  load: function load(aData) {
-    if (!this._active) {
-      return;
-    }
-
-    let params = {
-      namespace: "urn:flint:org.openflint.fling.media",
-      payload: {
-        type: "LOAD",
-        requestId: "requestId-2",
-        media: {
-          contentId: aData.source,
-          contentType: "video/mp4",
-          metadata: {
-            title: "",
-            subtitle: ""
-          }
-        }
-      }
-    };
-
-    this._sendMsg(params);
-  },
-
-  onStart: function(aContext) {
-    this._active = true;
-    if (this.listener && "onRemoteMediaStart" in this.listener) {
-      this.listener.onRemoteMediaStart(this);
-    }
-
-    this._ping();
-  },
-
-  onStop: function(aContext, aStatusCode) {
-    // This will be called for internal socket failures and timeouts. Make
-    // sure we cleanup.
-    this._teardown();
-  },
-
-  onAcknowledge: function(aContext, aSize) {},
-  onBinaryMessageAvailable: function(aContext, aMessage) {},
-
-  onMessageAvailable: function(aContext, aMessage) {
-    let msg = JSON.parse(aMessage);
-    if (!msg) {
-      return;
-    }
-
-    let payload = JSON.parse(msg.payload);
-    if (!payload) {
-      return;
-    }
-
-    // Handle state changes using the player notifications
-    if (payload.type == "MEDIA_STATUS") {
-      let status = payload.status[0];
-      let state = status.playerState.toLowerCase();
-      if (state == "playing") {
-        this._changeStatus("started");
-      } else if (state == "paused") {
-        this._changeStatus("paused");
-      } else if (state == "idle" && "idleReason" in status) {
-        // Make sure we are really finished. IDLE can be sent at other times.
-        let reason = status.idleReason.toLowerCase();
-        if (reason == "finished") {
-          this._changeStatus("completed");
-        }
-      }
-    }
-  },
-
-  onServerClose: function(aContext, aStatusCode, aReason) {
-    // This will be fired from _teardown when we close the websocket, but it
-    // can also be called for other internal socket failures and timeouts. We
-    // make sure the _teardown bails on reentry.
-    this._teardown();
-  }
-}
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -12,17 +12,16 @@ EXTRA_JS_MODULES += [
     'DelayedInit.jsm',
     'DownloadNotifications.jsm',
     'HelperApps.jsm',
     'Home.jsm',
     'HomeProvider.jsm',
     'JavaAddonManager.jsm',
     'JNI.jsm',
     'LightweightThemeConsumer.jsm',
-    'MatchstickApp.jsm',
     'MediaPlayerApp.jsm',
     'Messaging.jsm',
     'MulticastDNS.jsm',
     'NetErrorHelper.jsm',
     'Notifications.jsm',
     'OrderedBroadcast.jsm',
     'PageActions.jsm',
     'Prompt.jsm',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -895,16 +895,19 @@ pref("devtools.gcli.underscoreSrc", "htt
 // Set imgur upload client ID
 pref("devtools.gcli.imgurClientID", '0df414e888d7240');
 // Imgur's upload URL
 pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image");
 
 // GCLI commands directory
 pref("devtools.commands.dir", "");
 
+// Allows setting the performance marks for which telemetry metrics will be recorded.
+pref("devtools.telemetry.supported_performance_marks", "contentInteractive,navigationInteractive,navigationLoaded,visuallyLoaded,fullyLoaded,mediaEnumerated,scanEnd");
+
 // view source
 pref("view_source.syntax_highlight", true);
 pref("view_source.wrap_long_lines", false);
 pref("view_source.editor.external", false);
 pref("view_source.editor.path", "");
 // allows to add further arguments to the editor; use the %LINE% placeholder
 // for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
 pref("view_source.editor.args", "");
--- a/testing/marionette/client/marionette/tests/webapi-tests.ini
+++ b/testing/marionette/client/marionette/tests/webapi-tests.ini
@@ -17,12 +17,14 @@ skip = false
 [include:../../../../../dom/voicemail/test/marionette/manifest.ini]
 [include:../../../../../dom/battery/test/marionette/manifest.ini]
 [include:../../../../../dom/mobilemessage/tests/marionette/manifest.ini]
 [include:../../../../../dom/mobileconnection/tests/marionette/manifest.ini]
 [include:../../../../../dom/system/gonk/tests/marionette/manifest.ini]
 [include:../../../../../dom/icc/tests/marionette/manifest.ini]
 [include:../../../../../dom/system/tests/marionette/manifest.ini]
 [include:../../../../../dom/nfc/tests/marionette/manifest.ini]
+skip-if = android_version > '15' # Bug 1203072
 [include:../../../../../dom/events/test/marionette/manifest.ini]
 [include:../../../../../dom/wifi/test/marionette/manifest.ini]
 [include:../../../../../dom/cellbroadcast/tests/marionette/manifest.ini]
 [include:../../../../../dom/tethering/tests/marionette/manifest.ini]
+skip-if = android_version > '15' # Bug 1203075
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -73,17 +73,16 @@ class MochitestArguments(ArgumentContain
                   "browser with --keep-open=false",
           }],
         [["--appname"],
          {"dest": "app",
           "default": None,
           "help": "Override the default binary used to run tests with the path provided, e.g "
                   "/usr/bin/firefox. If you have run ./mach package beforehand, you can "
                   "specify 'dist' to run tests against the distribution bundle's binary.",
-          "suppress": build_obj is not None,
           }],
         [["--utility-path"],
          {"dest": "utilityPath",
           "default": build_obj.bindir if build_obj is not None else None,
           "help": "absolute path to directory containing utility programs "
                   "(xpcshell, ssltunnel, certutil)",
           "suppress": True,
           }],
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -34,16 +34,21 @@
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
   const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
   const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
 
+  // Use backslashes instead of forward slashes due to memory reporting's hacky
+  // handling of URLs.
+  const XUL_NS =
+    "http:\\\\www.mozilla.org\\keymaster\\gatekeeper\\there.is.only.xul";
+
   let vsizeAmounts = [];
   let residentAmounts = [];
   let heapAllocatedAmounts = [];
   let storageSqliteAmounts = [];
 
   let jsGcHeapUsedGcThingsTotal = 0;
   let jsGcHeapUsedGcThings = {};
 
@@ -114,23 +119,26 @@
     if (aPath.includes('<anonymized')) {
         present.anonymizedWhenUnnecessary = aPath;
     }
   }
 
   function handleReportAnonymized(aProcess, aPath, aKind, aUnits, aAmount,
                                   aDescription)
   {
+    // Path might include an xmlns using http, which is safe to ignore.
+    let reducedPath = aPath.replace(XUL_NS, "");
+
     // Shouldn't get http: or https: in any paths.
-    if (aPath.includes('http:')) {
+    if (reducedPath.includes('http:')) {
         present.httpWhenAnonymized = aPath;
     }
 
     // file: URLs should have their path anonymized.
-    if (aPath.search('file:..[^<]') !== -1) {
+    if (reducedPath.search('file:..[^<]') !== -1) {
         present.unanonymizedFilePathWhenAnonymized = aPath;
     }
   }
 
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
   // Access the distinguished amounts (mgr.explicit et al.) just to make sure
@@ -386,9 +394,8 @@
     }
   }
 
   test_register_weak();
 
   ]]>
   </script>
 </window>
-
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8885,16 +8885,135 @@
     "description": "Number of errors, keyed by appName."
   },
   "DEVTOOLS_HUD_WARNINGS": {
     "expires_in_version": "never",
     "kind": "count",
     "keyed": "true",
     "description": "Number of warnings, keyed by appName."
   },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_CONTENTINTERACTIVE": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'contentInteractive' performance mark, keyed by appName.",
+    "high": "2000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_NAVIGATIONINTERACTIVE": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'navigationInteractive' performance mark, keyed by appName.",
+    "high": "3000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_NAVIGATIONLOADED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'navigationLoaded' performance mark, keyed by appName.",
+    "high": "4000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_VISUALLYLOADED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'visuallyLoaded' performance mark, keyed by appName.",
+    "high": "5000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_MEDIAENUMERATED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'mediaEnumerated' performance mark, keyed by appName.",
+    "high": "5000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_FULLYLOADED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'fullyLoaded' performance mark, keyed by appName.",
+    "high": "30000",
+    "n_buckets": 30
+  },
+  "DEVTOOLS_HUD_APP_STARTUP_TIME_SCANEND": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The duration in ms between application launch and the 'scanEnd' performance mark, keyed by appName.",
+    "high": "30000",
+    "n_buckets": 30
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_CONTENTINTERACTIVE": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'contentInteractive' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "3000000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_NAVIGATIONINTERACTIVE": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'navigationInteractive' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "3000000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_NAVIGATIONLOADED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'navigationLoaded' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "3000000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_VISUALLYLOADED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'visuallyLoaded' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "3000000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_MEDIAENUMERATED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'mediaEnumerated' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "3000000",
+    "n_buckets": 10
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_FULLYLOADED": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'fullyLoaded' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "4000000",
+    "n_buckets": 20
+  },
+  "DEVTOOLS_HUD_APP_MEMORY_SCANEND": {
+    "expires_in_version": "never",
+    "kind": "linear",
+    "keyed": "true",
+    "description": "The USS memory consumed by an application at the time of the 'scanEnd' performance mark, keyed by appName.",
+    "low": "2000000",
+    "high": "4000000",
+    "n_buckets": 20
+  },
   "GRAPHICS_SANITY_TEST_REASON": {
     "alert_emails": ["danderson@mozilla.com"],
     "expires_in_version": "43",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
     "description": "Reports why a graphics sanity test was run. 0=First Run, 1=App Updated, 2=Device Change, 3=Driver Change."
   },
--- a/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
+++ b/toolkit/devtools/heapsnapshot/HeapSnapshot.cpp
@@ -23,20 +23,22 @@
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 #include "jsapi.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCRTGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
 #include "nsIOutputStream.h"
 #include "nsISupportsImpl.h"
 #include "nsNetUtil.h"
-#include "nsIFile.h"
+#include "nsPrintfCString.h"
 #include "prerror.h"
 #include "prio.h"
 #include "prtypes.h"
 
 namespace mozilla {
 namespace devtools {
 
 using namespace JS;
@@ -810,32 +812,65 @@ WriteHeapGraph(JSContext* cx,
 
 } // namespace devtools
 
 namespace dom {
 
 using namespace JS;
 using namespace devtools;
 
+static unsigned long
+msSinceProcessCreation(const TimeStamp& now)
+{
+  bool ignored;
+  auto duration = now - TimeStamp::ProcessCreation(ignored);
+  return (unsigned long) duration.ToMilliseconds();
+}
+
+// Creates the `$TEMP_DIR/XXXXXX-XXX.fxsnapshot` core dump file that heap
+// snapshots are serialized into.
+static already_AddRefed<nsIFile>
+createUniqueCoreDumpFile(ErrorResult& rv, const TimeStamp& now, nsAString& outFilePath)
+{
+  nsCOMPtr<nsIFile> file;
+  rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  auto ms = msSinceProcessCreation(now);
+  rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms));
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  rv = file->GetPath(outFilePath);
+  if (NS_WARN_IF(rv.Failed()))
+    return nullptr;
+
+  return file.forget();
+}
+
 /* static */ void
 ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
                                         JSContext* cx,
-                                        const nsAString& filePath,
                                         const HeapSnapshotBoundaries& boundaries,
+                                        nsAString& outFilePath,
                                         ErrorResult& rv)
 {
   auto start = TimeStamp::Now();
 
   bool wantNames = true;
   ZoneSet zones;
   uint32_t nodeCount = 0;
   uint32_t edgeCount = 0;
 
-  nsCOMPtr<nsIFile> file;
-  rv = NS_NewLocalFile(filePath, false, getter_AddRefs(file));
+  nsCOMPtr<nsIFile> file = createUniqueCoreDumpFile(rv, start, outFilePath);
   if (NS_WARN_IF(rv.Failed()))
     return;
 
   nsCOMPtr<nsIOutputStream> outputStream;
   rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
                                    PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
                                    -1, 0);
   if (NS_WARN_IF(rv.Failed()))
--- a/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html
+++ b/toolkit/devtools/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html
@@ -10,22 +10,16 @@ Bug 1024774 - Sanity test that we can ta
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
 <script>
 SimpleTest.waitForExplicitFinish();
 window.onload = function() {
   ok(ChromeUtils, "The ChromeUtils interface should be exposed in chrome windows.");
-
-  var file = Components.classes["@mozilla.org/file/directory_service;1"]
-    .getService(Components.interfaces.nsIProperties)
-    .get("CurWorkD", Components.interfaces.nsILocalFile);
-  file.append("core-dump.tmp");
-
-  ChromeUtils.saveHeapSnapshot(file.path, { runtime: true });
+  ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(true, "Should save a heap snapshot and shouldn't throw.");
   SimpleTest.finish();
 };
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/head_heapsnapshot.js
@@ -101,23 +101,20 @@ function getFilePath(aName, aAllowMissin
 
   if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
     path = path.replace(/\//g, "\\");
   }
 
   return path;
 }
 
-function saveNewHeapSnapshot(fileName=`core-dump-${Math.random()}.tmp`) {
-  const filePath = getFilePath(fileName, true, true);
+function saveNewHeapSnapshot() {
+  const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(filePath, "Should get a file path to save the core dump to.");
-
-  ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
   ok(true, "Saved a heap snapshot to " + filePath);
-
   return filePath;
 }
 
 /**
  * Save a heap snapshot to the file with the given name in the current
  * directory, read it back as a HeapSnapshot instance, and then take a census of
  * the heap snapshot's serialized heap graph with the provided census options.
  *
@@ -130,26 +127,20 @@ function saveNewHeapSnapshot(fileName=`c
  *        the Debugger's debuggees. If null, serialize the whole heap graph.
  *
  * @param {String} fileName
  *        The file name to save the heap snapshot's core dump file to, within
  *        the current directory.
  *
  * @returns Census
  */
-function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined,
-                                       // Add the Math.random() so that parallel
-                                       // tests are less likely to mess with
-                                       // each other.
-                                       fileName="core-dump-" + (Math.random()) + ".tmp") {
-  const filePath = getFilePath(fileName, true, true);
+function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) {
+  const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
+  const filePath = ChromeUtils.saveHeapSnapshot(snapshotOptions);
   ok(filePath, "Should get a file path to save the core dump to.");
-
-  const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
-  ChromeUtils.saveHeapSnapshot(filePath, snapshotOptions);
   ok(true, "Should have saved a heap snapshot to " + filePath);
 
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should have read a heap snapshot back from " + filePath);
   ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
 
   equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
   return snapshot.takeCensus(censusOptions);
--- a/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/heap-snapshot-worker.js
@@ -1,26 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 console.log("Initializing worker.");
 
 self.onmessage = e => {
   console.log("Starting test.");
   try {
-    const { filePath } = e.data;
-
     ok(typeof ChromeUtils === "undefined",
        "Should not have access to ChromeUtils in a worker.");
     ok(ThreadSafeChromeUtils,
        "Should have access to ThreadSafeChromeUtils in a worker.");
     ok(HeapSnapshot,
        "Should have access to HeapSnapshot in a worker.");
 
-    ThreadSafeChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+    const filePath = ThreadSafeChromeUtils.saveHeapSnapshot({ globals: [this] });
     ok(true, "Should be able to save a snapshot.");
 
     const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(filePath);
     ok(snapshot, "Should be able to read a heap snapshot");
     ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
   } catch (e) {
     ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
   } finally {
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js
@@ -4,20 +4,17 @@
 // Test that we can read core dumps into HeapSnapshot instances.
 
 if (typeof Debugger != "function") {
   const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   addDebuggerToGlobal(this);
 }
 
 function run_test() {
-  const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true);
-  ok(filePath, "Should get a file path");
-
-  ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+  const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
   ok(true, "Should be able to save a snapshot.");
 
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should be able to read a heap snapshot");
   ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
 
   do_test_finished();
 }
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js
@@ -20,20 +20,17 @@ function run_test() {
   debuggee.eval("this.objects = []");
   for (let i = 0; i < 100; i++) {
     debuggee.eval("this.objects.push({})");
   }
 
   // Now save a snapshot that will include the allocation stacks and read it
   // back again.
 
-  const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true);
-  ok(filePath, "Should get a file path");
-
-  ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
+  const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(true, "Should be able to save a snapshot.");
 
   const snapshot = ChromeUtils.readHeapSnapshot(filePath);
   ok(snapshot, "Should be able to read a heap snapshot");
   ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
 
   do_test_finished();
 }
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js
@@ -1,19 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that we can read core dumps into HeapSnapshot instances in a worker.
 
 add_task(function* () {
-  const filePath = getFilePath("core-dump-" + Math.random() + ".tmp", true, true);
-  ok(filePath, "Should get a file path");
-
   const worker = new ChromeWorker("resource://test/heap-snapshot-worker.js");
-  worker.postMessage({ filePath });
+  worker.postMessage({});
 
   let assertionCount = 0;
   worker.onmessage = e => {
     if (e.data.type !== "assertion") {
       return;
     }
 
     ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
--- a/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js
+++ b/toolkit/devtools/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js
@@ -6,94 +6,77 @@
 if (typeof Debugger != "function") {
   const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   addDebuggerToGlobal(this);
 }
 
 function run_test() {
   ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
 
-  let filePath = getFilePath("core-dump.tmp", true, true);
-  ok(filePath, "Should get a file path");
-
-  testBadParameters(filePath);
-  testGoodParameters(filePath);
+  testBadParameters();
+  testGoodParameters();
 
   do_test_finished();
 }
 
-function testBadParameters(filePath) {
+function testBadParameters() {
   throws(() => ChromeUtils.saveHeapSnapshot(),
          "Should throw if arguments aren't passed in.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(Object.create(null),
-                                            { runtime: true }),
-         "Should throw if the filePath is not coercible to string.");
-
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            null),
+  throws(() => ChromeUtils.saveHeapSnapshot(null),
          "Should throw if boundaries isn't an object.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            {}),
+  throws(() => ChromeUtils.saveHeapSnapshot({}),
          "Should throw if the boundaries object doesn't have any properties.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { runtime: true,
+  throws(() => ChromeUtils.saveHeapSnapshot({ runtime: true,
                                               globals: [this] }),
          "Should throw if the boundaries object has more than one property.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { debugger: {} }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ debugger: {} }),
          "Should throw if the debuggees object is not a Debugger object");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { globals: [{}] }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ globals: [{}] }),
          "Should throw if the globals array contains non-global objects.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { runtime: false }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ runtime: false }),
          "Should throw if runtime is supplied and is not true.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { globals: null }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ globals: null }),
          "Should throw if globals is not an object.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { globals: {} }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ globals: {} }),
          "Should throw if globals is not an array.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { debugger: Debugger.prototype }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ debugger: Debugger.prototype }),
          "Should throw if debugger is the Debugger.prototype object.");
 
-  throws(() => ChromeUtils.saveHeapSnapshot(filePath,
-                                            { get globals() { return [this]; } }),
+  throws(() => ChromeUtils.saveHeapSnapshot({ get globals() { return [this]; } }),
          "Should throw if boundaries property is a getter.");
 }
 
 const makeNewSandbox = () =>
   Cu.Sandbox(CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')());
 
-function testGoodParameters(filePath) {
+function testGoodParameters() {
   let sandbox = makeNewSandbox();
   let dbg = new Debugger(sandbox);
 
-  ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg });
+  ChromeUtils.saveHeapSnapshot({ debugger: dbg });
   ok(true, "Should be able to save a snapshot for a debuggee global.");
 
   dbg = new Debugger;
   let sandboxes = Array(10).fill(null).map(makeNewSandbox);
   sandboxes.forEach(sb => dbg.addDebuggee(sb));
 
-  ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg });
+  ChromeUtils.saveHeapSnapshot({ debugger: dbg });
   ok(true, "Should be able to save a snapshot for many debuggee globals.");
 
   dbg = new Debugger;
-  ChromeUtils.saveHeapSnapshot(filePath, { debugger: dbg });
+  ChromeUtils.saveHeapSnapshot({ debugger: dbg });
   ok(true, "Should be able to save a snapshot with no debuggee globals.");
 
-  ChromeUtils.saveHeapSnapshot(filePath, { globals: [this] });
+  ChromeUtils.saveHeapSnapshot({ globals: [this] });
   ok(true, "Should be able to save a snapshot for a specific global.");
 
-  ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
+  ChromeUtils.saveHeapSnapshot({ runtime: true });
   ok(true, "Should be able to save a snapshot of the full runtime.");
 }
--- a/toolkit/devtools/server/actors/call-watcher.js
+++ b/toolkit/devtools/server/actors/call-watcher.js
@@ -50,31 +50,34 @@ let FunctionCallActor = protocol.ActorCl
    *        The object owning the function when it was called.
    *        For example, in `foo.bar()`, the caller is `foo`.
    * @param number type
    *        Either METHOD_FUNCTION, METHOD_GETTER or METHOD_SETTER.
    * @param string name
    *        The called function's name.
    * @param array stack
    *        The called function's stack, as a list of { name, file, line } objects.
+   * @param number timestamp
+   *        The timestamp of draw-related functions
    * @param array args
    *        The called function's arguments.
    * @param any result
    *        The value returned by the function call.
    * @param boolean holdWeak
    *        Determines whether or not FunctionCallActor stores a weak reference
    *        to the underlying objects.
    */
-  initialize: function(conn, [window, global, caller, type, name, stack, args, result], holdWeak) {
+  initialize: function(conn, [window, global, caller, type, name, stack, timestamp, args, result], holdWeak) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.details = {
       type: type,
       name: name,
       stack: stack,
+      timestamp: timestamp
     };
 
     // Store a weak reference to all objects so we don't
     // prevent natural GC if `holdWeak` was passed into
     // setup as truthy. Used in the Web Audio Editor.
     if (holdWeak) {
       let weakRefs = {
         window: Cu.getWeakReference(window),
@@ -82,25 +85,27 @@ let FunctionCallActor = protocol.ActorCl
         result: Cu.getWeakReference(result),
         args: Cu.getWeakReference(args)
       };
 
       Object.defineProperties(this.details, {
         window: { get: () => weakRefs.window.get() },
         caller: { get: () => weakRefs.caller.get() },
         result: { get: () => weakRefs.result.get() },
-        args: { get: () => weakRefs.args.get() }
+        args: { get: () => weakRefs.args.get() },
+        timestamp: { get: () => weakRefs.timestamp.get() },
       });
     }
     // Otherwise, hold strong references to the objects.
     else {
       this.details.window = window;
       this.details.caller = caller;
       this.details.result = result;
       this.details.args = args;
+      this.details.timestamp = timestamp;
     }
 
     this.meta = {
       global: -1,
       previews: { caller: "", args: "" }
     };
 
     if (global == "WebGLRenderingContext") {
@@ -123,27 +128,28 @@ let FunctionCallActor = protocol.ActorCl
    */
   form: function() {
     return {
       actor: this.actorID,
       type: this.details.type,
       name: this.details.name,
       file: this.details.stack[0].file,
       line: this.details.stack[0].line,
+      timestamp: this.details.timestamp,
       callerPreview: this.meta.previews.caller,
       argsPreview: this.meta.previews.args
     };
   },
 
   /**
    * Gets more information about this function call, which is not necessarily
    * available on the Front instance.
    */
   getDetails: method(function() {
-    let { type, name, stack } = this.details;
+    let { type, name, stack, timestamp } = this.details;
 
     // Since not all calls on the stack have corresponding owner files (e.g.
     // callbacks of a requestAnimationFrame etc.), there's no benefit in
     // returning them, as the user can't jump to the Debugger from them.
     for (let i = stack.length - 1;;) {
       if (stack[i].file) {
         break;
       }
@@ -151,17 +157,18 @@ let FunctionCallActor = protocol.ActorCl
       i--;
     }
 
     // XXX: Use grips for objects and serialize them properly, in order
     // to add the function's caller, arguments and return value. Bug 978957.
     return {
       type: type,
       name: name,
-      stack: stack
+      stack: stack,
+      timestamp: timestamp
     };
   }, {
     response: { info: RetVal("call-details") }
   }),
 
   /**
    * Serializes the caller's name so that it can be easily be transferred
    * as a string, but still be useful when displayed in a potential UI.
@@ -238,16 +245,17 @@ let FunctionCallFront = protocol.FrontCl
    * to avoid extra roundtrips.
    */
   form: function(form) {
     this.actorID = form.actor;
     this.type = form.type;
     this.name = form.name;
     this.file = form.file;
     this.line = form.line;
+    this.timestamp = form.timestamp;
     this.callerPreview = form.callerPreview;
     this.argsPreview = form.argsPreview;
   }
 });
 
 /**
  * This actor observes function calls on certain objects or globals.
  */
@@ -329,16 +337,23 @@ let CallWatcherActor = exports.CallWatch
    */
   isRecording: method(function() {
     return this._recording;
   }, {
     response: RetVal("boolean")
   }),
 
   /**
+   * Initialize frame start timestamp for measuring
+   */
+  initFrameStartTimestamp: method(function() {
+    this._frameStartTimestamp = this.tabActor.window.performance.now();
+  }),
+
+  /**
    * Starts recording function calls.
    */
   resumeRecording: method(function() {
     this._recording = true;
   }),
 
   /**
    * Stops recording function calls.
@@ -419,19 +434,20 @@ let CallWatcherActor = exports.CallWatch
         let result;
         try {
           result = Cu.waiveXrays(originalFunc.apply(this, args));
         } catch (e) {
           throw createContentError(e, unwrappedWindow);
         }
 
         if (self._recording) {
+          let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp;
           let stack = getStack(name);
           let type = CallWatcherFront.METHOD_FUNCTION;
-          callback(unwrappedWindow, global, this, type, name, stack, args, result);
+          callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result);
         }
         return result;
       }, target, { defineAs: name });
 
       Object.defineProperty(target, name, {
         configurable: descriptor.configurable,
         enumerable: descriptor.enumerable,
         writable: true
@@ -448,30 +464,32 @@ let CallWatcherActor = exports.CallWatch
       let originalSetter = Cu.unwaiveXrays(target.__lookupSetter__(name));
 
       Object.defineProperty(target, name, {
         get: function(...args) {
           if (!originalGetter) return undefined;
           let result = Cu.waiveXrays(originalGetter.apply(this, args));
 
           if (self._recording) {
+            let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp;
             let stack = getStack(name);
             let type = CallWatcherFront.GETTER_FUNCTION;
-            callback(unwrappedWindow, global, this, type, name, stack, args, result);
+            callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result);
           }
           return result;
         },
         set: function(...args) {
           if (!originalSetter) return;
           originalSetter.apply(this, args);
 
           if (self._recording) {
+            let timestamp = self.tabActor.window.performance.now() - self._frameStartTimestamp;
             let stack = getStack(name);
             let type = CallWatcherFront.SETTER_FUNCTION;
-            callback(unwrappedWindow, global, this, type, name, stack, args, undefined);
+            callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, undefined);
           }
         },
         configurable: descriptor.configurable,
         enumerable: descriptor.enumerable
       });
     }
 
     /**
--- a/toolkit/devtools/server/actors/canvas.js
+++ b/toolkit/devtools/server/actors/canvas.js
@@ -311,16 +311,17 @@ let CanvasActor = exports.CanvasActor = 
    */
   recordAnimationFrame: method(function() {
     if (this._callWatcher.isRecording()) {
       return this._currentAnimationFrameSnapshot.promise;
     }
 
     this._recordingContainsDrawCall = false;
     this._callWatcher.eraseRecording();
+    this._callWatcher.initFrameStartTimestamp();
     this._callWatcher.resumeRecording();
 
     let deferred = this._currentAnimationFrameSnapshot = promise.defer();
     return deferred.promise;
   }, {
     response: { snapshot: RetVal("nullable:frame-snapshot") }
   }),
 
--- a/toolkit/devtools/shared/memory.js
+++ b/toolkit/devtools/shared/memory.js
@@ -134,18 +134,17 @@ let Memory = exports.Memory = Class({
 
   /**
    * Save a heap snapshot scoped to the current debuggees' portion of the heap
    * graph.
    *
    * @returns {String} The snapshot id.
    */
   saveHeapSnapshot: expectState("attached", function () {
-    const path = HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
-    ThreadSafeChromeUtils.saveHeapSnapshot(path, { debugger: this.dbg });
+    const path = ThreadSafeChromeUtils.saveHeapSnapshot({ debugger: this.dbg });
     return HeapSnapshotFileUtils.getSnapshotIdFromPath(path);
   }, "saveHeapSnapshot"),
 
   /**
    * Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
    * more information.
    */
   takeCensus: expectState("attached", function() {
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -2113,18 +2113,20 @@ this.XPIDatabaseReconcile = {
     let locationAddonMap = currentAddons.get(hideLocation);
     if (locationAddonMap) {
       for (let addon of locationAddonMap.values()) {
         addon.visible = false;
         addon.active = false;
       }
     }
 
-    // None of the active add-ons match the selected theme, enable the default.
-    if (!sawActiveTheme) {
+    // If a custom theme is selected and it wasn't seen in the new list of
+    // active add-ons then enable the default theme
+    if (XPIProvider.selectedSkin != XPIProvider.defaultSkin && !sawActiveTheme) {
+      logger.info("Didn't see selected skin " + XPIProvider.selectedSkin);
       XPIProvider.enableDefaultTheme();
     }
 
     // Finally update XPIStates to match everything
     for (let [locationName, locationAddonMap] of currentAddons) {
       for (let [id, addon] of locationAddonMap) {
         let xpiState = XPIStates.getAddon(locationName, id);
         xpiState.syncWithDB(addon);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_theme.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_theme.js
@@ -1084,12 +1084,56 @@ function run_test_21() {
         "theme1@tests.mozilla.org": [
           "onDisabling"
         ]
       });
 
       p1.userDisabled = false;
       ensure_test_completed();
 
-      end_test();
+      run_test_22();
     });
   }));
 }
+
+// Detecting a new add-on during the startup file check should not disable an
+// active lightweight theme
+function run_test_22() {
+  restartManager();
+
+  AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
+                               "1@personas.mozilla.org"], function([d, p1]) {
+    do_check_true(d.userDisabled);
+    do_check_false(d.appDisabled);
+    do_check_false(d.isActive);
+
+    do_check_false(p1.userDisabled);
+    do_check_false(p1.appDisabled);
+    do_check_true(p1.isActive);
+
+    writeInstallRDFForExtension({
+      id: "theme3@tests.mozilla.org",
+      version: "1.0",
+      name: "Test 3",
+      internalName: "theme3/1.0",
+      targetApplications: [{
+        id: "xpcshell@tests.mozilla.org",
+        minVersion: "1",
+        maxVersion: "2"
+      }]
+    }, profileDir);
+
+    restartManager();
+
+    AddonManager.getAddonsByIDs(["default@tests.mozilla.org",
+                                 "1@personas.mozilla.org"], function([d, p1]) {
+      do_check_true(d.userDisabled);
+      do_check_false(d.appDisabled);
+      do_check_false(d.isActive);
+
+      do_check_false(p1.userDisabled);
+      do_check_false(p1.appDisabled);
+      do_check_true(p1.isActive);
+
+      end_test();
+    });
+  });
+}