Merge m-c to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 25 Sep 2014 14:42:49 +0200
changeset 230473 128d8d99b72d227f4d37a2b4ed6bf1f002925856
parent 230472 e2c803c2aeec002102c76757fc1b6efdff769f68 (current diff)
parent 230416 e9e56750ca5b4a9ac8dbcedf253e298545fe9da9 (diff)
child 230474 0d3adc8bf11717022dceb7fcc7a1042b427428b2
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to mozilla-inbound
mobile/android/base/resources/drawable-large-hdpi-v11/favicon_none.png
mobile/android/base/resources/drawable-large-mdpi-v11/favicon_none.png
mobile/android/base/resources/drawable-large-xhdpi-v11/favicon_none.png
mobile/android/base/resources/drawable-large-xxhdpi-v11/favicon_none.png
--- a/b2g/chrome/content/payment.js
+++ b/b2g/chrome/content/payment.js
@@ -173,23 +173,25 @@ PaymentSettings.prototype = {
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != kMozSettingsChangedObserverTopic) {
       return;
     }
 
     try {
-      let setting = JSON.parse(aData);
-      if (!setting.key ||
-          (setting.key !== kRilDefaultDataServiceId &&
-           setting.key !== kRilDefaultPaymentServiceId)) {
+      if ('wrappedJSObject' in aSubject) {
+        aSubject = aSubject.wrappedJSObject;
+      }
+      if (!aSubject.key ||
+          (aSubject.key !== kRilDefaultDataServiceId &&
+           aSubject.key !== kRilDefaultPaymentServiceId)) {
         return;
       }
-      this.setServiceId(setting.key, setting.value);
+      this.setServiceId(aSubject.key, aSubject.value);
     } catch (e) {
       LOGE(e);
     }
   },
 
   cleanup: function() {
     Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
   }
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -327,16 +327,17 @@ var shell = {
 
     window.addEventListener('MozApplicationManifest', this);
     window.addEventListener('mozfullscreenchange', this);
     window.addEventListener('MozAfterPaint', this);
     window.addEventListener('sizemodechange', this);
     window.addEventListener('unload', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.addEventListener('mozbrowserselectionchange', this, true);
+    this.contentBrowser.addEventListener('mozbrowserscrollviewchange', this, true);
 
     CustomEventManager.init();
     WebappsHelper.init();
     UserAgentOverrides.init();
     IndexedDBPromptHelper.init();
     CaptivePortalLoginHelper.init();
 
     this.contentBrowser.src = homeURL;
@@ -354,16 +355,17 @@ var shell = {
     window.removeEventListener('keydown', this, true);
     window.removeEventListener('keypress', this, true);
     window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('mozfullscreenchange', this);
     window.removeEventListener('sizemodechange', this);
     this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.removeEventListener('mozbrowserselectionchange', this, true);
+    this.contentBrowser.removeEventListener('mozbrowserscrollviewchange', this, true);
     ppmm.removeMessageListener("content-handler", this);
 
     UserAgentOverrides.uninit();
     IndexedDBPromptHelper.uninit();
   },
 
   // If this key event actually represents a hardware button, filter it here
   // and send a mozChromeEvent with detail.type set to xxx-button-press or
@@ -495,17 +497,22 @@ var shell = {
         break;
       case 'mozbrowserlocationchange':
         if (content.document.location == 'about:blank') {
           return;
         }
 
         this.notifyContentStart();
        break;
-
+      case 'mozbrowserscrollviewchange':
+        this.sendChromeEvent({
+          type: 'scrollviewchange',
+          detail: evt.detail,
+        });
+        break;
       case 'mozbrowserselectionchange':
         // The mozbrowserselectionchange event, may have crossed the chrome-content boundary.
         // This event always dispatch to shell.js. But the offset we got from this event is
         // based on tab's coordinate. So get the actual offsets between shell and evt.target.
         let elt = evt.target;
         let win = elt.ownerDocument.defaultView;
         let offsetX = win.mozInnerScreenX - window.mozInnerScreenX;
         let offsetY = win.mozInnerScreenY - window.mozInnerScreenY;
--- a/b2g/components/MobileIdentityUIGlue.js
+++ b/b2g/components/MobileIdentityUIGlue.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict"
 
 const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/ContentRequestHelper.jsm");
 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
 Cu.import("resource://gre/modules/MobileIdentityUIGlueCommon.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
--- 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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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
@@ -10,27 +10,27 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--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="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- 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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -10,27 +10,27 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--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="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- 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="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- 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/flame/sources.xml
+++ b/b2g/config/flame/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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <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"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "89fc79354058663937beda2c0eb657e87354a872", 
+    "revision": "15291949ab37f96d1d1e30bb890a2604b4454894", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -8,26 +8,26 @@
   <!--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"/>
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -6,21 +6,21 @@
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--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"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,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="8986df0f82e15ac2798df0b6c2ee3435400677ac">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <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/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -8,26 +8,26 @@
   <!--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"/>
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
-  <project name="platform_build" path="build" remote="b2g" revision="4d1e85908d792d9468c4da7040acd191fbb51b40">
+  <project name="platform_build" path="build" remote="b2g" revision="84923f1940625c47ff4c1fdf01b10fde3b7d909e">
     <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="03d7bcad57ea281869976a9aed0a38849f7c8bc5"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5d2e2f4ebf5f370d6003517057dcd47493dec90"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5883a99b6528ced9dafaed8d3ca2405fb285537e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="837a62c581254345ad97276386757f775a5872bb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="20a1521efdac44c8219f00c2414de031891fb464"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -301,16 +301,25 @@ var gPluginHandler = {
   hideNotificationBar: function (browser, name) {
     let notificationBox = gBrowser.getNotificationBox(browser);
     let notification = notificationBox.getNotificationWithValue(name);
     if (notification)
       notificationBox.removeNotification(notification, true);
   },
 
   updateHiddenPluginUI: function (browser, haveInsecure, actions, principal, host) {
+    // It is possible that we've received a message from the frame script to show
+    // the hidden plugin notification for a principal that no longer matches the one
+    // that the browser's content now has assigned (ie, the browser has browsed away
+    // after the message was sent, but before the message was received). In that case,
+    // we should just ignore the message.
+    if (!principal.equals(browser.contentPrincipal)) {
+      return;
+    }
+
     // Set up the icon
     document.getElementById("plugins-notification-icon").classList.
       toggle("plugin-blocked", haveInsecure);
 
     // Now configure the notification bar
     let notificationBox = gBrowser.getNotificationBox(browser);
 
     function hideNotification() {
--- a/browser/base/content/newtab/intro.js
+++ b/browser/base/content/newtab/intro.js
@@ -36,17 +36,17 @@ let gIntro = {
       // Point the panel at the 'what' menu item
       this._nodes.panel.openPopup(nodes.what);
     });
   },
 
   _setUpPanel: function() {
     // Build the panel if necessary
     if (this._nodes.panel.childNodes.length == 1) {
-      ['<a href="' + TILES_EXPLAIN_LINK + '">' + newTabString("learn.link") + "</a>",
+      ['<a href="' + TILES_INTRO_LINK + '">' + newTabString("learn.link") + "</a>",
        '<a href="' + TILES_PRIVACY_LINK + '">' + newTabString("privacy.link") + "</a>",
        '<input type="button" class="newtab-customize"/>',
       ].forEach((arg, index) => {
         let paragraph = document.createElementNS(HTML_NAMESPACE, "p");
         this._nodes.panel.appendChild(paragraph);
         paragraph.innerHTML = newTabString("intro.paragraph" + (index + 1), [arg]);
       });
     }
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -181,33 +181,32 @@ input[type=button] {
 .newtab-title {
   font-size: 13px;
   left: 0;
   padding-top: 14px;
   text-overflow: ellipsis;
 }
 
 .newtab-sponsored {
-  background-color: #f9f9f9;
   border: 1px solid #dcdcdc;
   border-radius: 2px;
-  color: #9b9b9b;
   cursor: pointer;
   display: none;
   font-family: Arial;
   font-size: 10px;
   height: 17px;
   line-height: 17px;
   margin-bottom: -1px;
   padding: 0 4px;
 }
 
 .newtab-sponsored:-moz-any(:hover, [active]) {
-  background-color: #dcdcdc;
-  color: #666666;
+  background-color: #3a72b1;
+  border: 0;
+  color: white;
 }
 
 .newtab-sponsored:-moz-locale-dir(rtl) {
   left: 0;
   right: auto;
 }
 
 .newtab-site:-moz-any([type=enhanced], [type=sponsored]) .newtab-sponsored {
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -45,17 +45,18 @@ function newTabString(name, args) {
 
 function inPrivateBrowsingMode() {
   return PrivateBrowsingUtils.isWindowPrivate(window);
 }
 
 const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
 const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-sponsored-tiles-work";
+const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-tiles-work-firefox";
+const TILES_INTRO_LINK = "https://www.mozilla.org/firefox/tiles/";
 const TILES_PRIVACY_LINK = "https://www.mozilla.org/privacy/";
 
 #include transformations.js
 #include page.js
 #include grid.js
 #include cells.js
 #include sites.js
 #include drag.js
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -575,19 +575,16 @@
               Array.unshift(arguments, this.mBrowser);
               return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
             },
 
             _shouldShowProgress: function (aRequest) {
               if (this.mBlank)
                 return false;
 
-              if (gMultiProcessBrowser)
-                return true;
-
               // Don't show progress indicators in tabs for about: URIs
               // pointing to local resources.
               try {
                 let channel = aRequest.QueryInterface(Ci.nsIChannel);
                 if (channel.originalURI.schemeIs("about") &&
                     (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
                   return false;
               } catch (e) {}
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -283,16 +283,17 @@ skip-if = e10s # Bug 916974 - Session hi
 [browser_bug902156.js]
 skip-if = e10s
 [browser_bug906190.js]
 skip-if = buildapp == "mulet" || e10s # Bug ?????? - test directly manipulates content (strange - gets an element from a child which it tries to treat as a string, but that fails)
 [browser_bug970746.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content)
 [browser_bug1015721.js]
 skip-if = os == 'win' || e10s # Bug 1056146 - FullZoomHelper uses promiseTabLoadEvent() which isn't e10s friendly
+[browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug ?????? - [JavaScript Error: "Error in AboutHome.sendAboutHomeData TypeError: target.messageManager is undefined" {file: "resource:///modules/AboutHome.jsm" line: 208}]
 [browser_contentAreaClick.js]
 skip-if = e10s
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" || e10s # bug 967013, bug 926729
 [browser_ctrlTab.js]
 skip-if = e10s # Bug ????? - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function(){
+  // Test that changing the URL in a pinned tab works correctly
+
+  let TEST_LINK_INITIAL = "about:";
+  let TEST_LINK_CHANGED = "about:support";
+
+  let appTab = gBrowser.addTab(TEST_LINK_INITIAL);
+  gBrowser.pinTab(appTab);
+  is(appTab.pinned, true, "Tab was successfully pinned");
+
+  let initialTabsNo = gBrowser.tabs.length;
+
+  let goButton = document.getElementById("urlbar-go-button");
+  gBrowser.selectedTab = appTab;
+  gURLBar.focus();
+  gURLBar.value = TEST_LINK_CHANGED;
+
+  let promisePageload = promiseTabLoadEvent(appTab);
+  goButton.click();
+  yield promisePageload;
+
+  is(appTab.linkedBrowser.currentURI.spec, TEST_LINK_CHANGED,
+     "New page loaded in the app tab");
+  is(gBrowser.tabs.length, initialTabsNo, "No additional tabs were opened");
+});
+
+registerCleanupFunction(function () {
+  gBrowser.removeTab(gBrowser.selectedTab);
+});
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -28,16 +28,17 @@ support-files =
   plugin_clickToPlayDeny.html
   plugin_data_url.html
   plugin_hidden_to_visible.html
   plugin_iframe.html
   plugin_outsideScrollArea.html
   plugin_overlayed.html
   plugin_positioned.html
   plugin_small.html
+  plugin_small_2.html
   plugin_syncRemoved.html
   plugin_test.html
   plugin_test2.html
   plugin_test3.html
   plugin_two_types.html
   plugin_unknown.html
   plugin_crashCommentAndURL.html
   plugin_zoom.html
@@ -57,16 +58,17 @@ run-if = crashreporter
 [browser_CTP_data_urls.js]
 [browser_CTP_drag_drop.js]
 [browser_CTP_hide_overlay.js]
 [browser_CTP_iframe.js]
 [browser_CTP_multi_allow.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_notificationBar.js]
 [browser_CTP_outsideScrollArea.js]
+[browser_CTP_remove_navigate.js]
 [browser_CTP_resize.js]
 [browser_CTP_zoom.js]
 [browser_globalplugin_crashinfobar.js]
 [browser_pageInfo_plugins.js]
 [browser_pluginnotification.js]
 [browser_pluginplaypreview.js]
 [browser_pluginplaypreview2.js]
 [browser_pluginCrashCommentAndURL.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
@@ -0,0 +1,82 @@
+/* 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 gTestRoot = getRootDirectory(gTestPath);
+const gHttpTestRoot = gTestRoot.replace("chrome://mochitests/content/",
+                                        "http://127.0.0.1:8888/");
+
+add_task(function* () {
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+  });
+})
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page, that we don't show the hidden plugin
+ * notification bar on the new page.
+ */
+add_task(function* () {
+  let newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  let browser = gBrowser.selectedBrowser;
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  // Load up a page with a plugin...
+  let notificationPromise =
+    waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+  yield loadPage(browser, gHttpTestRoot + "plugin_small.html")
+  yield forcePluginBindingAttached(browser);
+  yield notificationPromise;
+
+  // Trigger the PluginRemoved event to be fired, and then immediately
+  // browse to a new page.
+  let plugin = browser.contentDocument.getElementById("test");
+  plugin.remove();
+  yield loadPage(browser, "about:mozilla");
+
+  // There should be no hidden plugin notification bar at about:mozilla.
+  let notificationBox = gBrowser.getNotificationBox(browser);
+  is(notificationBox.getNotificationWithValue("plugin-hidden"), null,
+     "Expected no notification box");
+  gBrowser.removeTab(newTab);
+});
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page with a plugin, that we show the right notification
+ * for the new page.
+ */
+add_task(function* () {
+  let newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  let browser = gBrowser.selectedBrowser;
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY,
+                            "Second Test Plug-in");
+
+  // Load up a page with a plugin...
+  let notificationPromise =
+    waitForNotificationBar("plugin-hidden", browser);
+  yield loadPage(browser, gHttpTestRoot + "plugin_small.html")
+  yield forcePluginBindingAttached(browser);
+  yield notificationPromise;
+
+  // Trigger the PluginRemoved event to be fired, and then immediately
+  // browse to a new page.
+  let plugin = browser.contentDocument.getElementById("test");
+  plugin.remove();
+  yield loadPage(browser, gTestRoot + "plugin_small_2.html");
+  let notification = yield waitForNotificationBar("plugin-hidden", browser);
+  ok(notification, "There should be a notification shown for the new page.");
+
+  // Ensure that the notification is showing information about
+  // the x-second-test plugin.
+  ok(notification.label.contains("Second Test"), "Should mention the second plugin");
+  ok(!notification.label.contains("127.0.0.1"), "Should not refer to old principal");
+  ok(notification.label.contains("null"), "Should refer to the new principal");
+  gBrowser.removeTab(newTab);
+});
--- a/browser/base/content/test/plugins/head.js
+++ b/browser/base/content/test/plugins/head.js
@@ -116,20 +116,83 @@ function waitForNotificationPopup(notifi
     () => {
       ok(notification, `Successfully got the ${notificationID} notification popup`);
       callback(notification);
     },
     `Waited too long for the ${notificationID} notification popup`
   );
 }
 
+/**
+ * Returns a Promise that resolves when a notification bar
+ * for a browser is shown. Alternatively, for old-style callers,
+ * can automatically call a callback before it resolves.
+ *
+ * @param notificationID
+ *        The ID of the notification to look for.
+ * @param browser
+ *        The browser to check for the notification bar.
+ * @param callback (optional)
+ *        A function to be called just before the Promise resolves.
+ *
+ * @return Promise
+ */
 function waitForNotificationBar(notificationID, browser, callback) {
-  let notification;
-  let notificationBox = gBrowser.getNotificationBox(browser);
-  waitForCondition(
-    () => (notification = notificationBox.getNotificationWithValue(notificationID)),
-    () => {
-      ok(notification, `Successfully got the ${notificationID} notification bar`);
-      callback(notification);
-    },
-    `Waited too long for the ${notificationID} notification bar`
-  );
+  return new Promise((resolve, reject) => {
+    let notification;
+    let notificationBox = gBrowser.getNotificationBox(browser);
+    waitForCondition(
+      () => (notification = notificationBox.getNotificationWithValue(notificationID)),
+      () => {
+        ok(notification, `Successfully got the ${notificationID} notification bar`);
+        if (callback) {
+          callback(notification);
+        }
+        resolve(notification);
+      },
+      `Waited too long for the ${notificationID} notification bar`
+    );
+  });
 }
+
+/**
+ * Due to layout being async, "PluginBindAttached" may trigger later.
+ * This returns a Promise that resolves once we've forced a layout
+ * flush, which triggers the PluginBindAttached event to fire.
+ *
+ * @param browser
+ *        The browser to force plugin bindings in.
+ *
+ * @return Promise
+ */
+function forcePluginBindingAttached(browser) {
+  return new Promise((resolve, reject) => {
+    let doc = browser.contentDocument;
+    let elems = doc.getElementsByTagName('embed');
+    if (elems.length < 1) {
+      elems = doc.getElementsByTagName('object');
+    }
+    elems[0].clientTop;
+    executeSoon(resolve);
+  });
+}
+
+/**
+ * Loads a page in a browser, and returns a Promise that
+ * resolves once the "load" event has been fired for that
+ * browser.
+ *
+ * @param browser
+ *        The browser to load the page in.
+ * @param uri
+ *        The URI to load.
+ *
+ * @return Promise
+ */
+function loadPage(browser, uri) {
+  return new Promise((resolve, reject) => {
+    browser.addEventListener("load", function onLoad(event) {
+      browser.removeEventListener("load", onLoad, true);
+      resolve();
+    }, true);
+    browser.loadURI(uri);
+  });
+}
copy from browser/base/content/test/plugins/plugin_small.html
copy to browser/base/content/test/plugins/plugin_small_2.html
--- a/browser/base/content/test/plugins/plugin_small.html
+++ b/browser/base/content/test/plugins/plugin_small_2.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="utf-8">
 </head>
 <body>
-<embed id="test" style="width: 10px; height: 10px" type="application/x-test">
+<embed id="test" style="width: 10px; height: 10px" type="application/x-second-test">
 </body>
 </html>
--- a/browser/devtools/canvasdebugger/panel.js
+++ b/browser/devtools/canvasdebugger/panel.js
@@ -61,12 +61,14 @@ CanvasDebuggerPanel.prototype = {
 
   destroy: function() {
     // Make sure this panel is not already destroyed.
     if (this._destroyer) {
       return this._destroyer;
     }
 
     return this._destroyer = this.panelWin.shutdownCanvasDebugger().then(() => {
+      // Destroy front to ensure packet handler is removed from client
+      this.panelWin.gFront.destroy();
       this.emit("destroyed");
     });
   }
 };
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -27,16 +27,17 @@ skip-if = e10s # Bug 1070837 - devtools/
 # [browser_toolbox_raise.js] # Bug 962258
 # skip-if = os == "win"
 [browser_toolbox_ready.js]
 [browser_toolbox_select_event.js]
 skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
 [browser_toolbox_sidebar.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_tool_ready.js]
+[browser_toolbox_tool_remote_reopen.js]
 [browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
 [browser_toolbox_window_title_changes.js]
 [browser_toolbox_zoom.js]
 [browser_toolbox_custom_host.js]
 [browser_toolbox_theme_registration.js]
 
 # We want this test to run for mochitest-dt as well, so we include it here:
--- a/browser/devtools/framework/test/browser_toolbox_tool_ready.js
+++ b/browser/devtools/framework/test/browser_toolbox_tool_ready.js
@@ -1,41 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
-  addTab("about:blank").then(function(tab) {
-    let target = TargetFactory.forTab(tab);
-    target.makeRemote().then(performChecks.bind(null, target));
-  }).then(null, console.error);
+function performChecks(target) {
+  return Task.spawn(function() {
+    let toolIds = gDevTools.getToolDefinitionArray()
+                           .filter(def => def.isTargetSupported(target))
+                           .map(def => def.id);
 
-  function performChecks(target) {
-    let toolIds = gDevTools.getToolDefinitionArray()
-                    .filter(def => def.isTargetSupported(target))
-                    .map(def => def.id);
-
-    let open = function(index) {
+    let toolbox;
+    for (let index = 0; index < toolIds.length; index++) {
       let toolId = toolIds[index];
 
       info("About to open " + index + "/" + toolId);
-      gDevTools.showToolbox(target, toolId).then(function(toolbox) {
-        ok(toolbox, "toolbox exists for " + toolId);
-        is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
+      toolbox = yield gDevTools.showToolbox(target, toolId);
+      ok(toolbox, "toolbox exists for " + toolId);
+      is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
 
-        let panel = toolbox.getCurrentPanel();
-        ok(panel.isReady, toolId + " panel should be ready");
+      let panel = toolbox.getCurrentPanel();
+      ok(panel.isReady, toolId + " panel should be ready");
+    }
+
+    yield toolbox.destroy();
+  });
+}
 
-        let nextIndex = index + 1;
-        if (nextIndex >= toolIds.length) {
-          toolbox.destroy().then(function() {
-            gBrowser.removeCurrentTab();
-            finish();
-          });
-        }
-        else {
-          open(nextIndex);
-        }
-      }, console.error);
-    };
-
-    open(0);
-  }
+function test() {
+  Task.spawn(function() {
+    toggleAllTools(true);
+    let tab = yield addTab("about:blank");
+    let target = TargetFactory.forTab(tab);
+    yield target.makeRemote();
+    yield performChecks(target);
+    gBrowser.removeCurrentTab();
+    toggleAllTools(false);
+    finish();
+  }, console.error);
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_tool_remote_reopen.js
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { DebuggerServer } =
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+const { DebuggerClient } =
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+
+/**
+ * Bug 979536: Ensure fronts are destroyed after toolbox close.
+ *
+ * The fronts need to be destroyed manually to unbind their onPacket handlers.
+ *
+ * When you initialize a front and call |this.manage|, it adds a client actor
+ * pool that the DebuggerClient uses to route packet replies to that actor.
+ *
+ * Most (all?) tools create a new front when they are opened.  When the destroy
+ * step is skipped and the tool is reopened, a second front is created and also
+ * added to the client actor pool.  When a packet reply is received, is ends up
+ * being routed to the first (now unwanted) front that is still in the client
+ * actor pool.  Since this is not the same front that was used to make the
+ * request, an error occurs.
+ *
+ * This problem does not occur with the toolbox for a local tab because the
+ * toolbox target creates its own DebuggerClient for the local tab, and the
+ * client is destroyed when the toolbox is closed, which removes the client
+ * actor pools, and avoids this issue.
+ *
+ * In WebIDE, we do not destroy the DebuggerClient on toolbox close because it
+ * is still used for other purposes like managing apps, etc. that aren't part of
+ * a toolbox.  Thus, the same client gets reused across multiple toolboxes,
+ * which leads to the tools failing if they don't destroy their fronts.
+ */
+
+function runTools(target) {
+  return Task.spawn(function() {
+    let toolIds = gDevTools.getToolDefinitionArray()
+                           .filter(def => def.isTargetSupported(target))
+                           .map(def => def.id);
+
+    let toolbox;
+    for (let index = 0; index < toolIds.length; index++) {
+      let toolId = toolIds[index];
+
+      info("About to open " + index + "/" + toolId);
+      toolbox = yield gDevTools.showToolbox(target, toolId, "window");
+      ok(toolbox, "toolbox exists for " + toolId);
+      is(toolbox.currentToolId, toolId, "currentToolId should be " + toolId);
+
+      let panel = toolbox.getCurrentPanel();
+      ok(panel.isReady, toolId + " panel should be ready");
+    }
+
+    yield toolbox.destroy();
+  });
+}
+
+function getClient() {
+  let deferred = promise.defer();
+
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+  }
+
+  let transport = DebuggerServer.connectPipe();
+  let client = new DebuggerClient(transport);
+
+  client.connect(() => {
+    deferred.resolve(client);
+  });
+
+  return deferred.promise;
+}
+
+function getTarget(client) {
+  let deferred = promise.defer();
+
+  let tabList = client.listTabs(tabList => {
+    let target = TargetFactory.forRemoteTab({
+      client: client,
+      form: tabList.tabs[tabList.selected],
+      chrome: false
+    });
+    deferred.resolve(target);
+  });
+
+  return deferred.promise;
+}
+
+function test() {
+  Task.spawn(function() {
+    toggleAllTools(true);
+    yield addTab("about:blank");
+
+    let client = yield getClient();
+    let target = yield getTarget(client);
+    yield runTools(target);
+
+    // Actor fronts should be destroyed now that the toolbox has closed, but
+    // look for any that remain.
+    for (let pool of client.__pools) {
+      if (!pool.__poolMap) {
+        continue;
+      }
+      for (let actor of pool.__poolMap.keys()) {
+        // Bug 1056342: Profiler fails today because of framerate actor, but
+        // this appears more complex to rework, so leave it for that bug to
+        // resolve.
+        if (actor.contains("framerateActor")) {
+          todo(false, "Front for " + actor + " still held in pool!");
+          continue;
+        }
+        ok(false, "Front for " + actor + " still held in pool!");
+      }
+    }
+
+    gBrowser.removeCurrentTab();
+    DebuggerServer.destroy();
+    toggleAllTools(false);
+    finish();
+  }, console.error);
+}
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -122,8 +122,21 @@ function once(target, eventName, useCapt
   return deferred.promise;
 }
 
 function waitForTick() {
   let deferred = promise.defer();
   executeSoon(deferred.resolve);
   return deferred.promise;
 }
+
+function toggleAllTools(state) {
+  for (let [, tool] of gDevTools._tools) {
+    if (!tool.visibilityswitch) {
+      continue;
+    }
+    if (state) {
+      Services.prefs.setBoolPref(tool.visibilityswitch, true);
+    } else {
+      Services.prefs.clearUserPref(tool.visibilityswitch);
+    }
+  }
+}
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -1537,16 +1537,19 @@ Toolbox.prototype = {
     outstanding.push(this.destroyInspector().then(() => {
       // Removing buttons
       if (this._pickerButton) {
         this._pickerButton.removeEventListener("command", this._togglePicker, false);
         this._pickerButton = null;
       }
     }));
 
+    // We need to grab a reference to win before this._host is destroyed.
+    let win = this.frame.ownerGlobal;
+
     // Remove the host UI
     outstanding.push(this.destroyHost());
 
     if (this._requisition) {
       this._requisition.destroy();
     }
     this._telemetry.toolClosed("toolbox");
     this._telemetry.destroy();
@@ -1564,19 +1567,16 @@ Toolbox.prototype = {
       let target = this._target;
       this._target = null;
       this.highlighterUtils.release();
       target.off("close", this.destroy);
       return target.destroy();
     }, console.error).then(() => {
       this.emit("destroyed");
 
-      // We need to grab a reference to win before this._host is destroyed.
-      let win = this.frame.ownerGlobal;
-
       // Free _host after the call to destroyed in order to let a chance
       // to destroyed listeners to still query toolbox attributes
       this._host = null;
       this._toolPanels.clear();
 
       // Force GC to prevent long GC pauses when running tests and to free up
       // memory in general when the toolbox is closed.
       if (gDevTools.testing) {
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -31,16 +31,17 @@ const MINIMUM_FONT_SIZE = 6;
 const NORMAL_FONT_SIZE = 12;
 
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
 const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
 const TAB_SIZE = "devtools.editor.tabsize";
+const FALLBACK_CHARSET_LIST = "intl.fallbackCharsetList.ISO-8859-1";
 
 const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
 const require   = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 
 const Telemetry = require("devtools/shared/telemetry");
 const Editor    = require("devtools/sourceeditor/editor");
 const TargetFactory = require("devtools/framework/target").TargetFactory;
@@ -1048,16 +1049,60 @@ var Scratchpad = {
       if (aCallback) {
         aCallback.call(this, Components.results.NS_ERROR_UNEXPECTED);
       }
     });
 
   },
 
   /**
+   * Get a list of applicable charsets.
+   * The best charset, defaulting to "UTF-8"
+   *
+   * @param string aBestCharset
+   * @return array of strings
+   */
+  _getApplicableCharsets: function SP__getApplicableCharsets(aBestCharset="UTF-8") {
+    let charsets = Services.prefs.getCharPref(
+      FALLBACK_CHARSET_LIST).split(",").filter(function (value) {
+      return value.length;
+    });
+    charsets.unshift(aBestCharset);
+    return charsets;
+  },
+
+  /**
+   * Get content converted to unicode, using a list of input charset to try.
+   *
+   * @param string aContent
+   * @param array of string aCharsetArray
+   * @return string
+   */
+  _getUnicodeContent: function SP__getUnicodeContent(aContent, aCharsetArray) {
+    let content = null,
+        converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter),
+        success = aCharsetArray.some(charset => {
+          try {
+            converter.charset = charset;
+            content = converter.ConvertToUnicode(aContent);
+            return true;
+          } catch (e) {
+            this.notificationBox.appendNotification(
+              this.strings.formatStringFromName("importFromFile.convert.failed",
+                                                [ charset ], 1),
+              "file-import-convert-failed",
+              null,
+              this.notificationBox.PRIORITY_WARNING_HIGH,
+              null);
+          }
+        });
+    return content;
+  },
+
+  /**
    * Read the content of a file and put it into the textbox.
    *
    * @param nsILocalFile aFile
    *        The file you want to save the textbox content into.
    * @param boolean aSilentError
    *        True if you do not want to display an error when file load fails,
    *        false otherwise.
    * @param function aCallback
@@ -1067,27 +1112,42 @@ var Scratchpad = {
    *        2) the data that was read from the file, if any.
    */
   importFromFile: function SP_importFromFile(aFile, aSilentError, aCallback)
   {
     // Prevent file type detection.
     let channel = NetUtil.newChannel(aFile);
     channel.contentType = "application/javascript";
 
+    this.notificationBox.removeAllNotifications(false);
+
     NetUtil.asyncFetch(channel, (aInputStream, aStatus) => {
       let content = null;
 
       if (Components.isSuccessCode(aStatus)) {
-        let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                        createInstance(Ci.nsIScriptableUnicodeConverter);
-        converter.charset = "UTF-8";
+        let charsets = this._getApplicableCharsets();
         content = NetUtil.readInputStreamToString(aInputStream,
                                                   aInputStream.available());
-        content = converter.ConvertToUnicode(content);
-
+        content = this._getUnicodeContent(content, charsets);
+        if (!content) {
+          let message = this.strings.formatStringFromName(
+            "importFromFile.convert.failed",
+            [ charsets.join(", ") ],
+            1);
+          this.notificationBox.appendNotification(
+            message,
+            "file-import-convert-failed",
+            null,
+            this.notificationBox.PRIORITY_CRITICAL_MEDIUM,
+            null);
+          if (aCallback) {
+            aCallback.call(this, aStatus, content);
+          }
+          return;
+        }
         // Check to see if the first line is a mode-line comment.
         let line = content.split("\n")[0];
         let modeline = this._scanModeLine(line);
         let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
 
         if (chrome && modeline["-sp-context"] === "browser") {
           this.setBrowserContext();
         }
@@ -1095,17 +1155,18 @@ var Scratchpad = {
         this.editor.setText(content);
         this.editor.clearHistory();
         this.dirty = false;
         document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
       }
       else if (!aSilentError) {
         window.alert(this.strings.GetStringFromName("openFile.failed"));
       }
-
+      this.setFilename(aFile.path);
+      this.setRecentFile(aFile);
       if (aCallback) {
         aCallback.call(this, aStatus, content);
       }
     });
   },
 
   /**
    * Open a file to edit in the Scratchpad.
@@ -1140,19 +1201,17 @@ var Scratchpad = {
               null,
               this.notificationBox.PRIORITY_WARNING_HIGH,
               null);
 
             this.clearFiles(aIndex, 1);
             return;
           }
 
-          this.setFilename(file.path);
           this.importFromFile(file, false);
-          this.setRecentFile(file);
         }
       });
     };
 
     if (aIndex > -1) {
       promptCallback();
     } else {
       let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt
@@ -0,0 +1,2 @@
+Typ	Datum	Uhrzeit	Quelle	Kategorie	Ereignis	Benutzer	Computer
+Informationen	10.08.2012	16:07:11	MSDTC	Datenträger 	2444	Nicht zutreffend	
--- a/browser/devtools/scratchpad/test/browser.ini
+++ b/browser/devtools/scratchpad/test/browser.ini
@@ -28,16 +28,18 @@ skip-if = buildapp == 'mulet'
 [browser_scratchpad_contexts.js]
 [browser_scratchpad_execute_print.js]
 [browser_scratchpad_files.js]
 [browser_scratchpad_initialization.js]
 [browser_scratchpad_inspect.js]
 [browser_scratchpad_inspect_primitives.js]
 [browser_scratchpad_long_string.js]
 [browser_scratchpad_open.js]
+# test file:
+[NS_ERROR_ILLEGAL_INPUT.txt]
 [browser_scratchpad_open_error_console.js]
 [browser_scratchpad_throw_output.js]
 [browser_scratchpad_pprint-02.js]
 [browser_scratchpad_pprint.js]
 [browser_scratchpad_pprint_error_goto_line.js]
 [browser_scratchpad_restore.js]
 [browser_scratchpad_tab_switch.js]
 [browser_scratchpad_ui.js]
--- a/browser/devtools/scratchpad/test/browser_scratchpad_open.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js
@@ -1,30 +1,31 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // only finish() when correct number of tests are done
-const expected = 3;
+const expected = 4;
 var count = 0;
 var lastUniqueName = null;
 
 function done()
 {
   if (++count == expected) {
     finish();
   }
 }
 
 function test()
 {
   waitForExplicitFinish();
   testOpen();
   testOpenWithState();
   testOpenInvalidState();
+  testOpenTestFile();
 }
 
 function testUniqueName(name)
 {
   ok(name, "Scratchpad has a uniqueName");
 
   if (lastUniqueName === null) {
     lastUniqueName = name;
@@ -69,8 +70,32 @@ function testOpenWithState()
 }
 
 function testOpenInvalidState()
 {
   let win = openScratchpad(null, {state: 7});
   ok(!win, "no scratchpad opened if state is not an object");
   done();
 }
+
+function testOpenTestFile()
+{
+  let win = openScratchpad(function(win) {
+    ok(win, "scratchpad opened for file open");
+    try {
+      win.Scratchpad.importFromFile(
+        "http://example.com/browser/browser/devtools/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt",
+        "silent",
+        function (aStatus, content) {
+          let nb = win.document.querySelector('#scratchpad-notificationbox');
+          is(nb.querySelectorAll('notification').length, 1, "There is just one notification");
+          let cn = nb.currentNotification;
+          is(cn.priority, nb.PRIORITY_WARNING_HIGH, "notification priority is correct");
+          is(cn.value, "file-import-convert-failed", "notification value is corrent");
+          is(cn.type, "warning", "notification type is correct");
+          done();
+        });
+      ok(true, "importFromFile does not cause exception");
+    } catch (exception) {
+      ok(false, "importFromFile causes exception " + DevToolsUtils.safeErrorString(exception));
+    }
+  }, {noFocus: true});
+}
--- a/browser/devtools/shadereditor/panel.js
+++ b/browser/devtools/shadereditor/panel.js
@@ -61,12 +61,14 @@ ShaderEditorPanel.prototype = {
 
   destroy: function() {
     // Make sure this panel is not already destroyed.
     if (this._destroyer) {
       return this._destroyer;
     }
 
     return this._destroyer = this.panelWin.shutdownShaderEditor().then(() => {
+      // Destroy front to ensure packet handler is removed from client
+      this.panelWin.gFront.destroy();
       this.emit("destroyed");
     });
   }
 };
--- a/browser/devtools/storage/panel.js
+++ b/browser/devtools/storage/panel.js
@@ -59,16 +59,18 @@ StoragePanel.prototype = {
   },
 
   /**
    * Destroy the style editor.
    */
   destroy: function() {
     if (!this._destroyed) {
       this.UI.destroy();
+      // Destroy front to ensure packet handler is removed from client
+      this._front.destroy();
       this._destroyed = true;
 
       this._target.off("close", this.destroy);
       this._target = null;
       this._toolbox = null;
       this._panelDoc = null;
     }
 
--- a/browser/devtools/storage/ui.js
+++ b/browser/devtools/storage/ui.js
@@ -91,17 +91,17 @@ exports.StorageUI = StorageUI;
 
 StorageUI.prototype = {
 
   storageTypes: null,
   shouldResetColumns: true,
 
   destroy: function() {
     this.front.off("stores-update", this.onUpdate);
-    this._panelDoc.removeEventListener("keypress", this.handleKeypress)
+    this._panelDoc.removeEventListener("keypress", this.handleKeypress);
   },
 
   /**
    * Empties and hides the object viewer sidebar
    */
   hideSidebar: function() {
     this.view.empty();
     this.sidebar.hidden = true;
--- a/browser/devtools/timeline/panel.js
+++ b/browser/devtools/timeline/panel.js
@@ -52,12 +52,14 @@ TimelinePanel.prototype = {
 
   destroy: Task.async(function*() {
     // Make sure this panel is not already destroyed.
     if (this._destroyed) {
       return;
     }
 
     yield this.panelWin.shutdownTimeline();
+    // Destroy front to ensure packet handler is removed from client
+    this.panelWin.gFront.destroy();
     this.emit("destroyed");
     this._destroyed = true;
   })
 };
--- a/browser/devtools/webaudioeditor/panel.js
+++ b/browser/devtools/webaudioeditor/panel.js
@@ -56,12 +56,14 @@ WebAudioEditorPanel.prototype = {
 
   destroy: function() {
     // Make sure this panel is not already destroyed.
     if (this._destroyer) {
       return this._destroyer;
     }
 
     return this._destroyer = this.panelWin.shutdownWebAudioEditor().then(() => {
+      // Destroy front to ensure packet handler is removed from client
+      this.panelWin.gFront.destroy();
       this.emit("destroyed");
     });
   }
 };
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-bypass.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-bypass.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#bypass(), AudioNode#isBypassed()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
   let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
     front.setup({ reload: true }),
     get3(front, "create-node")
   ]);
 
   is((yield gainNode.isBypassed()), false, "Nodes start off unbypassed.");
 
   info("Calling node#bypass(true)");
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-param-flags.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-param-flags.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getParamFlags()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let allNodeParams = yield Promise.all(nodes.map(node => node.getParams()));
   let nodeTypes = [
     "AudioDestinationNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getParams()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let allNodeParams = yield Promise.all(nodes.map(node => node.getParams()));
   let nodeTypes = [
     "AudioDestinationNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-params-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that default properties are returned with the correct type
  * from the AudioNode actors.
  */
 
 function spawnTest() {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let allParams = yield Promise.all(nodes.map(node => node.getParams()));
   let types = [
     "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-set-param.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-set-param.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getParam() / AudioNode#setParam()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
   let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
     front.setup({ reload: true }),
     get3(front, "create-node")
   ]);
 
   let freq = yield oscNode.getParam("frequency");
   info(typeof freq);
   ise(freq, 440, "AudioNode:getParam correctly fetches AudioParam");
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-type.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-type.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#getType()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
   let expectedTypes = [
     "AudioDestinationNode",
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-is-source.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-is-source.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test AudioNode#isSource()
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
+  let { target, front } = yield initBackend(SIMPLE_NODES_URL);
   let [_, nodes] = yield Promise.all([
     front.setup({ reload: true }),
     getN(front, "create-node", 14)
   ]);
 
   let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
   let isSourceResult = yield Promise.all(nodes.map(node => node.isSource()));
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_destroy-node-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_destroy-node-01.js
@@ -5,17 +5,17 @@
  * Tests that the destruction node event is fired and that the nodes are no
  * longer stored internally in the tool, that the graph is updated properly, and
  * that selecting a soon-to-be dead node clears the inspector.
  *
  * All done in one test since this test takes a few seconds to clear GC.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(DESTROY_NODES_URL);
+  let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let destroyed = getN(gAudioNodes, "remove", 10);
--- a/browser/devtools/webaudioeditor/test/browser_wa_first-run.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_first-run.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the reloading/onContentLoaded hooks work.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { gFront, $ } = panel.panelWin;
 
   is($("#reload-notice").hidden, false,
     "The 'reload this page' notice should initially be visible.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-click.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-click.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the clicking on a node in the GraphView opens and sets
  * the correct node in the InspectorView
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let panelWin = panel.panelWin;
   let { gFront, $, $$, InspectorView } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors, _] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-markers.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-markers.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the SVG marker styling is updated when devtools theme changes.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, MARKER_STYLING } = panelWin;
 
   let currentTheme = Services.prefs.getCharPref("devtools.theme");
 
   ok(MARKER_STYLING.light, "Marker styling exists for light theme.");
   ok(MARKER_STYLING.dark, "Marker styling exists for dark theme.");
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-01.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests that SVG nodes and edges were created for the Graph View.
  */
 
 let connectCount = 0;
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   gAudioNodes.on("connect", onConnectNode);
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests more edge rendering for complex graphs.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$ } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-03.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-03.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests to ensure that selected nodes stay selected on graph redraw.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_TOGGLE_URL);
+  let { target, panel } = yield initWebAudioEditor(CONNECT_TOGGLE_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-04.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-04.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests audio param connection rendering.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL);
+  let { target, panel } = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-render-05.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-render-05.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests to ensure that param connections trigger graph redraws
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_TOGGLE_PARAM_URL);
+  let { target, panel } = yield initWebAudioEditor(CONNECT_TOGGLE_PARAM_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 1, 0)
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-selected.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-selected.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that SVG nodes and edges were created for the Graph View.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
   let [actors] = yield Promise.all([
--- a/browser/devtools/webaudioeditor/test/browser_wa_graph-zoom.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_graph-zoom.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the graph's scale and position is reset on a page reload.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, ContextView } = panelWin;
 
   let started = once(gFront, "start-context");
 
   yield Promise.all([
     reload(target),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_inspector-toggle.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector-toggle.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the inspector toggle button shows and hides
  * the inspector panel as intended.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_inspector.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that inspector view opens on graph node click, and
  * loads the correct node inside the inspector.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_navigate.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_navigate.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests naviating from a page to another will repopulate
  * the audio graph if both pages have an AudioContext.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $ } = panelWin;
 
   reload(target);
 
   var [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-01.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that properties are updated when modifying the VariablesView.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-edit-02.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that properties are not updated when modifying the VariablesView.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
@@ -30,17 +30,17 @@ function waitForDeviceClosed() {
       deferred.resolve();
     }
   });
 
   return deferred.promise;
 }
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(MEDIA_NODES_URL);
+  let { target, panel } = yield initWebAudioEditor(MEDIA_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   // Auto enable getUserMedia
   let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION);
   Services.prefs.setBoolPref(MEDIA_PERMISSION, true);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params-objects.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params-objects.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that params view correctly displays non-primitive properties
  * like AudioBuffer and Float32Array in properties of AudioNodes.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
+  let { target, panel } = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-params.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that params view correctly displays all properties for nodes
  * correctly, with default values and correct types.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_NODES_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_properties-view.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that params view shows params when they exist, and are hidden otherwise.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-01.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that reloading a tab will properly listen for the `start-context`
  * event and reshow the tools after reloading.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { gFront, $ } = panel.panelWin;
 
   is($("#reload-notice").hidden, false,
     "The 'reload this page' notice should initially be visible.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-02.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-02.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests reloading a tab with the tools open properly cleans up
  * the graph.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $ } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-03.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-03.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests reloading a tab with the tools open properly cleans up
  * the inspector and selected node.
  */
 
 function spawnTest() {
-  let [target, debuggee, panel] = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, InspectorView } = panelWin;
 
   reload(target);
 
   let [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
--- a/browser/devtools/webaudioeditor/test/browser_wa_reset-04.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_reset-04.js
@@ -3,17 +3,17 @@
 
 /**
  * Tests that switching to an iframe works fine.
  */
 
 function spawnTest() {
   Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
 
-  let [target, debuggee, panel, toolbox] = yield initWebAudioEditor(IFRAME_CONTEXT_URL);
+  let { target, panel, toolbox } = yield initWebAudioEditor(IFRAME_CONTEXT_URL);
   let { gFront, $ } = panel.panelWin;
 
   is($("#reload-notice").hidden, false,
     "The 'reload this page' notice should initially be visible.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should initially be hidden.");
   is($("#content").hidden, true,
     "The tool's content should initially be hidden.");
--- a/browser/devtools/webaudioeditor/test/browser_webaudio-actor-connect-param.js
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-connect-param.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test the `connect-param` event on the web audio actor.
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(CONNECT_PARAM_URL);
+  let { target, front } = yield initBackend(CONNECT_PARAM_URL);
   let [, , [destNode, carrierNode, modNode, gainNode], , connectParam] = yield Promise.all([
     front.setup({ reload: true }),
     once(front, "start-context"),
     getN(front, "create-node", 4),
     get2(front, "connect-node"),
     once(front, "connect-param")
   ]);
 
--- a/browser/devtools/webaudioeditor/test/browser_webaudio-actor-destroy-node.js
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-destroy-node.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test `destroy-node` event on WebAudioActor.
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(DESTROY_NODES_URL);
+  let { target, front } = yield initBackend(DESTROY_NODES_URL);
 
   let waitUntilDestroyed = getN(front, "destroy-node", 10);
   let [, , created] = yield Promise.all([
     front.setup({ reload: true }),
     once(front, "start-context"),
     // Should create 1 destination node and 10 disposable buffer nodes
     getN(front, "create-node", 13)
   ]);
--- a/browser/devtools/webaudioeditor/test/browser_webaudio-actor-simple.js
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-simple.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test basic communication of Web Audio actor
  */
 
 function spawnTest () {
-  let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
   let [_, __, [destNode, oscNode, gainNode], [connect1, connect2]] = yield Promise.all([
     front.setup({ reload: true }),
     once(front, "start-context"),
     get3(front, "create-node"),
     get2(front, "connect-node")
   ]);
 
   let destType = yield destNode.getType();
--- a/browser/devtools/webaudioeditor/test/head.js
+++ b/browser/devtools/webaudioeditor/test/head.js
@@ -133,39 +133,37 @@ function initBackend(aUrl) {
   if (!DebuggerServer.initialized) {
     DebuggerServer.init(() => true);
     DebuggerServer.addBrowserActors();
   }
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     let front = new WebAudioFront(target.client, target.form);
-    return [target, debuggee, front];
+    return { target, front };
   });
 }
 
 function initWebAudioEditor(aUrl) {
   info("Initializing a web audio editor pane.");
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
-    let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
     let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor");
     let panel = toolbox.getCurrentPanel();
-    return [target, debuggee, panel, toolbox];
+    return { target, panel, toolbox };
   });
 }
 
 function teardown(aPanel) {
   info("Destroying the web audio editor.");
 
   return Promise.all([
     once(aPanel, "destroyed"),
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -57,17 +57,17 @@ let UI = {
 
     this.updateCommands();
     this.updateRuntimeList();
 
     this.onfocus = this.onfocus.bind(this);
     window.addEventListener("focus", this.onfocus, true);
 
     AppProjects.load().then(() => {
-      this.openLastProject();
+      this.autoSelectProject();
     });
 
     // Auto install the ADB Addon Helper. Only once.
     // If the user decides to uninstall the addon, we won't install it again.
     let autoInstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
     if (autoInstallADBHelper && !Devices.helperAddonInstalled) {
       GetAvailableAddons().then(addons => {
         addons.adb.install();
@@ -75,31 +75,16 @@ let UI = {
     }
     Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
 
     this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
 
     this.setupDeck();
   },
 
-  openLastProject: function() {
-    let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
-    let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
-    if (lastProjectLocation && shouldRestore) {
-      let lastProject = AppProjects.get(lastProjectLocation);
-      if (lastProject) {
-        AppManager.selectedProject = lastProject;
-      } else {
-        AppManager.selectedProject = null;
-      }
-    } else {
-      AppManager.selectedProject = null;
-    }
-  },
-
   uninit: function() {
     window.removeEventListener("focus", this.onfocus, true);
     AppManager.off("app-manager-update", this.appManagerUpdate);
     AppManager.uninit();
     window.removeEventListener("message", this.onMessage);
   },
 
   canWindowClose: function() {
@@ -136,16 +121,17 @@ let UI = {
       case "project":
         this._updatePromise = Task.spawn(function() {
           UI.updateTitle();
           yield UI.destroyToolbox();
           UI.updateCommands();
           UI.updateProjectButton();
           UI.openProject();
           UI.autoStartProject();
+          UI.saveLastSelectedProject();
         });
         return;
       case "project-is-not-running":
       case "project-is-running":
       case "list-tabs-response":
         this.updateCommands();
         break;
       case "runtime":
@@ -155,16 +141,20 @@ let UI = {
       case "project-validated":
         this.updateTitle();
         this.updateCommands();
         this.updateProjectButton();
         this.updateProjectEditorHeader();
         break;
       case "install-progress":
         this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
+        break;
+      case "runtime-apps-found":
+        this.autoSelectProject();
+        break;
     };
     this._updatePromise = promise.resolve();
   },
 
   openInBrowser: function(url) {
     // Open a URL in a Firefox window
     let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
     if (browserWin) {
@@ -492,22 +482,16 @@ let UI = {
 
     // Nothing to show
 
     if (!project) {
       this.resetDeck();
       return;
     }
 
-    // Save last project location
-
-    if (project.location) {
-      Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
-    }
-
     // Make sure the directory exist before we show Project Editor
 
     let forceDetailsOnly = false;
     if (project.type == "packaged") {
       forceDetailsOnly = !utils.doesFileExist(project.location);
     }
 
     // Show only the details screen
@@ -565,16 +549,99 @@ let UI = {
 
     // Validate project
     yield AppManager.validateProject(project);
 
     // Select project
     AppManager.selectedProject = project;
   }),
 
+  // Remember the last selected project on the runtime
+  saveLastSelectedProject: function() {
+    let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
+    if (!shouldRestore) {
+      return;
+    }
+
+    // Ignore unselection of project on runtime disconnection
+    if (AppManager.connection.status != Connection.Status.CONNECTED) {
+      return;
+    }
+
+    let project = "", type = "";
+    let selected = AppManager.selectedProject;
+    if (selected) {
+      if (selected.type == "runtimeApp") {
+        type = "runtimeApp";
+        project = selected.app.manifestURL;
+      } else if (selected.type == "mainProcess") {
+        type = "mainProcess";
+      } else if (selected.type == "packaged" ||
+                 selected.type == "hosted") {
+        type = "local";
+        project = selected.location;
+      }
+    }
+    if (type) {
+      Services.prefs.setCharPref("devtools.webide.lastSelectedProject",
+                                 type + ":" + project);
+    } else {
+      Services.prefs.clearUserPref("devtools.webide.lastSelectedProject");
+    }
+  },
+
+  autoSelectProject: function() {
+    if (AppManager.selectedProject) {
+      return;
+    }
+    let shouldRestore = Services.prefs.getBoolPref("devtools.webide.restoreLastProject");
+    if (!shouldRestore) {
+      return;
+    }
+    let pref = Services.prefs.getCharPref("devtools.webide.lastSelectedProject");
+    if (!pref) {
+      return;
+    }
+    let m = pref.match(/^(\w+):(.*)$/);
+    if (!m) {
+      return;
+    }
+    let [_, type, project] = m;
+
+    if (type == "local") {
+      let lastProject = AppProjects.get(project);
+      if (lastProject) {
+        AppManager.selectedProject = lastProject;
+      }
+    }
+
+    // For other project types, we need to be connected to the runtime
+    if (AppManager.connection.status != Connection.Status.CONNECTED) {
+      return;
+    }
+
+    if (type == "mainProcess" && AppManager.isMainProcessDebuggable()) {
+      AppManager.selectedProject = {
+        type: "mainProcess",
+        name: Strings.GetStringFromName("mainProcess_label"),
+        icon: AppManager.DEFAULT_PROJECT_ICON
+      }
+    } else if (type == "runtimeApp") {
+      let app = AppManager.apps.get(project);
+      if (app) {
+        AppManager.selectedProject = {
+          type: "runtimeApp",
+          app: app.manifest,
+          icon: app.iconURL,
+          name: app.manifest.name
+        };
+      }
+    }
+  },
+
   /********** DECK **********/
 
   setupDeck: function() {
     let iframes = document.querySelectorAll("#deck > iframe");
     for (let iframe of iframes) {
       iframe.tooltip = "aHTMLTooltip";
     }
   },
@@ -882,19 +949,22 @@ let Cmds = {
         panelNode.addEventListener("popupshown", onPopupShown);
         panelNode.openPopup(anchorNode);
         panelVboxNode.scrollTop = 0;
       }, 0);
     }, deferred.reject);
 
 
     let runtimeappsHeaderNode = document.querySelector("#panel-header-runtimeapps");
-    let sortedApps = AppManager.webAppsStore.object.all;
+    let sortedApps = [];
+    for (let [manifestURL, app] of AppManager.apps) {
+      sortedApps.push(app);
+    }
     sortedApps = sortedApps.sort((a, b) => {
-      return a.name > b.name;
+      return a.manifest.name > b.manifest.name;
     });
     let mainProcess = AppManager.isMainProcessDebuggable();
     if (AppManager.connection.status == Connection.Status.CONNECTED &&
         (sortedApps.length > 0 || mainProcess)) {
       runtimeappsHeaderNode.removeAttribute("hidden");
     } else {
       runtimeappsHeaderNode.setAttribute("hidden", "true");
     }
@@ -919,26 +989,26 @@ let Cmds = {
         };
       }, true);
     }
 
     for (let i = 0; i < sortedApps.length; i++) {
       let app = sortedApps[i];
       let panelItemNode = document.createElement("toolbarbutton");
       panelItemNode.className = "panel-item";
-      panelItemNode.setAttribute("label", app.name);
+      panelItemNode.setAttribute("label", app.manifest.name);
       panelItemNode.setAttribute("image", app.iconURL);
       runtimeAppsNode.appendChild(panelItemNode);
       panelItemNode.addEventListener("click", () => {
         UI.hidePanels();
         AppManager.selectedProject = {
           type: "runtimeApp",
-          app: app,
+          app: app.manifest,
           icon: app.iconURL,
-          name: app.name
+          name: app.manifest.name
         };
       }, true);
     }
 
     // Build the tab list right now, so it's fast...
     this._buildProjectPanelTabs();
 
     // But re-list them and rebuild, in case any tabs navigated since the last
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -9,21 +9,20 @@ let { Promise: promise } = Cu.import("re
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
 const {TextEncoder, OS}  = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {AppProjects} = require("devtools/app-manager/app-projects");
-const WebappsStore = require("devtools/app-manager/webapps-store");
 const TabStore = require("devtools/webide/tab-store");
 const {AppValidator} = require("devtools/app-manager/app-validator");
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
-const AppActorFront = require("devtools/app-actor-front");
+const {AppActorFront} = require("devtools/app-actor-front");
 const {getDeviceFront} = require("devtools/server/actors/device");
 const {getPreferenceFront} = require("devtools/server/actors/preference");
 const {setTimeout} = require("sdk/timers");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 const {USBRuntime, WiFiRuntime, SimulatorRuntime,
        gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
 const discovery = require("devtools/toolkit/discovery/discovery");
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
@@ -41,19 +40,16 @@ exports.AppManager = AppManager = {
   init: function() {
     let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
     let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
 
     this.connection = ConnectionManager.createConnection("localhost", port);
     this.onConnectionChanged = this.onConnectionChanged.bind(this);
     this.connection.on(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
 
-    this.onWebAppsStoreready = this.onWebAppsStoreready.bind(this);
-    this.webAppsStore = new WebappsStore(this.connection);
-    this.webAppsStore.on("store-ready", this.onWebAppsStoreready);
     this.tabStore = new TabStore(this.connection);
     this.onTabNavigate = this.onTabNavigate.bind(this);
     this.onTabClosed = this.onTabClosed.bind(this);
     this.tabStore.on("navigate", this.onTabNavigate);
     this.tabStore.on("closed", this.onTabClosed);
 
     this.runtimeList = {
       usb: [],
@@ -64,35 +60,28 @@ exports.AppManager = AppManager = {
     if (Services.prefs.getBoolPref("devtools.webide.enableLocalRuntime")) {
       this.runtimeList.custom.push(gLocalRuntime);
     }
     this.trackUSBRuntimes();
     this.trackWiFiRuntimes();
     this.trackSimulatorRuntimes();
 
     this.onInstallProgress = this.onInstallProgress.bind(this);
-    AppActorFront.on("install-progress", this.onInstallProgress);
 
     this.observe = this.observe.bind(this);
     Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
   },
 
   uninit: function() {
-    AppActorFront.off("install-progress", this.onInstallProgress);
-    this._unlistenToApps();
     this.selectedProject = null;
     this.selectedRuntime = null;
     this.untrackUSBRuntimes();
     this.untrackWiFiRuntimes();
     this.untrackSimulatorRuntimes();
-    this._runningApps.clear();
     this.runtimeList = null;
-    this.webAppsStore.off("store-ready", this.onWebAppsStoreready);
-    this.webAppsStore.destroy();
-    this.webAppsStore = null;
     this.tabStore.off("navigate", this.onTabNavigate);
     this.tabStore.off("closed", this.onTabClosed);
     this.tabStore.destroy();
     this.tabStore = null;
     this.connection.off(Connection.Events.STATUS_CHANGED, this.onConnectionChanged);
     this._listTabsResponse = null;
     this.connection.disconnect();
     this.connection = null;
@@ -131,89 +120,62 @@ exports.AppManager = AppManager = {
 
   onConnectionChanged: function() {
     if (this.connection.status == Connection.Status.DISCONNECTED) {
       this.selectedRuntime = null;
     }
 
     if (this.connection.status != Connection.Status.CONNECTED) {
       console.log("Connection status changed: " + this.connection.status);
-      this._runningApps.clear();
-      this._unlistenToApps();
+      if (this._appsFront) {
+        this._appsFront.off("install-progress", this.onInstallProgress);
+        this._appsFront.unwatchApps();
+        this._appsFront = null;
+      }
       this._listTabsResponse = null;
     } else {
       this.connection.client.listTabs((response) => {
-        this._listenToApps();
+        let front = new AppActorFront(this.connection.client,
+                                      response);
+        front.on("install-progress", this.onInstallProgress);
+        front.watchApps(() => this.checkIfProjectIsRunning())
+             .then(() => front.fetchIcons())
+             .then(() => {
+               this._appsFront = front;
+               this.checkIfProjectIsRunning();
+               this.update("runtime-apps-found");
+             });
         this._listTabsResponse = response;
-        this._getRunningApps();
         this.update("list-tabs-response");
       });
     }
 
     this.update("connection");
   },
 
-  onInstallProgress: function(event, details) {
-    this.update("install-progress", details);
-  },
-
-  onWebAppsStoreready: function() {
-    this.update("runtime-apps-found");
+  get apps() {
+    if (this._appsFront) {
+      return this._appsFront.apps;
+    } else {
+      return new Map();
+    }
   },
 
-  _runningApps: new Set(),
-  _getRunningApps: function() {
-    let client = this.connection.client;
-    if (!this._listTabsResponse.webappsActor) {
-      return;
-    }
-    let request = {
-      to: this._listTabsResponse.webappsActor,
-      type: "listRunningApps"
-    };
-    client.request(request, (res) => {
-      if (res.error) {
-        this.reportError("error_listRunningApps");
-        console.error("listRunningApps error: " + res.error);
-      }
-      for (let m of res.apps) {
-        this._runningApps.add(m);
-      }
-    });
-    this.checkIfProjectIsRunning();
-  },
-  _listenToApps: function() {
-    let client = this.connection.client;
-    client.addListener("appOpen", (type, { manifestURL }) => {
-      this._runningApps.add(manifestURL);
-      this.checkIfProjectIsRunning();
-    });
-
-    client.addListener("appClose", (type, { manifestURL }) => {
-      this._runningApps.delete(manifestURL);
-      this.checkIfProjectIsRunning();
-    });
-
-    client.addListener("appUninstall", (type, { manifestURL }) => {
-      this._runningApps.delete(manifestURL);
-      this.checkIfProjectIsRunning();
-    });
-  },
-  _unlistenToApps: function() {
-    // Is that even possible?
-    // connection.client is null now.
+  onInstallProgress: function(event, details) {
+    this.update("install-progress", details);
   },
 
   isProjectRunning: function() {
     if (this.selectedProject.type == "mainProcess" ||
         this.selectedProject.type == "tab") {
       return true;
     }
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    return manifest && this._runningApps.has(manifest);
+
+    let app = this._getProjectFront(this.selectedProject);
+    return app && app.running;
   },
 
   checkIfProjectIsRunning: function() {
     if (this.selectedProject) {
       if (this.isProjectRunning()) {
         this.update("project-is-running");
       } else {
         this.update("project-is-not-running");
@@ -255,63 +217,56 @@ exports.AppManager = AppManager = {
   },
 
   reloadTab: function() {
     if (this.selectedProject && this.selectedProject.type != "tab") {
       return promise.reject("tried to reload non-tab project");
     }
     return this.getTarget().then(target => {
       target.activeTab.reload();
-    });
+    }, console.error.bind(console));
   },
 
   getTarget: function() {
-    let client = this.connection.client;
-
     if (this.selectedProject.type == "mainProcess") {
       return devtools.TargetFactory.forRemoteTab({
         form: this._listTabsResponse,
         client: this.connection.client,
         chrome: true
       });
     }
 
     if (this.selectedProject.type == "tab") {
       return this.tabStore.getTargetForTab();
     }
 
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    if (!manifest) {
-      console.error("Can't find manifestURL for selected project");
-      return promise.reject();
+    let app = this._getProjectFront(this.selectedProject);
+    if (!app) {
+      return promise.reject("Can't find app front for selected project");
     }
 
-    let actor = this._listTabsResponse.webappsActor;
     return Task.spawn(function* () {
       // Once we asked the app to launch, the app isn't necessary completely loaded.
       // launch request only ask the app to launch and immediatly returns.
       // We have to keep trying to get app tab actors required to create its target.
 
       for (let i = 0; i < 10; i++) {
         try {
-          let target = yield AppActorFront.getTargetForApp(client, actor, manifest);
-          // Success
-          return target;
+          return yield app.getTarget();
         } catch(e) {}
         let deferred = promise.defer();
         setTimeout(deferred.resolve, 500);
         yield deferred.promise;
       }
 
-      AppManager.reportError("error_cantConnectToApp", manifest);
+      AppManager.reportError("error_cantConnectToApp", app.manifest.manifestURL);
       throw new Error("can't connect to app");
     });
   },
 
-
   getProjectManifestURL: function(project) {
     let manifest = null;
     if (project.type == "runtimeApp") {
       manifest = project.app.manifestURL;
     }
 
     if (project.type == "hosted") {
       manifest = project.location;
@@ -319,16 +274,24 @@ exports.AppManager = AppManager = {
 
     if (project.type == "packaged" && project.packagedAppOrigin) {
       manifest = "app://" + project.packagedAppOrigin + "/manifest.webapp";
     }
 
     return manifest;
   },
 
+  _getProjectFront: function(project) {
+    let manifest = this.getProjectManifestURL(project);
+    if (manifest && this._appsFront) {
+      return this._appsFront.apps.get(manifest);
+    }
+    return null;
+  },
+
   _selectedProject: null,
   set selectedProject(value) {
     // A regular comparison still sees a difference when equal in some cases
     if (JSON.stringify(this._selectedProject) !==
         JSON.stringify(value)) {
       this._selectedProject = value;
 
       // Clear out tab store's selected state, if any
@@ -440,33 +403,29 @@ exports.AppManager = AppManager = {
     this.connection.disconnect();
     return deferred.promise;
   },
 
   launchRuntimeApp: function() {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
       return promise.reject("attempting to launch a non-runtime app");
     }
-    let client = this.connection.client;
-    let actor = this._listTabsResponse.webappsActor;
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    return AppActorFront.launchApp(client, actor, manifest);
+    let app = this._getProjectFront(this.selectedProject);
+    return app.launch();
   },
 
   launchOrReloadRuntimeApp: function() {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
       return promise.reject("attempting to launch / reload a non-runtime app");
     }
-    let client = this.connection.client;
-    let actor = this._listTabsResponse.webappsActor;
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    if (!this.isProjectRunning()) {
-      return AppActorFront.launchApp(client, actor, manifest);
+    let app = this._getProjectFront(this.selectedProject);
+    if (!app.running) {
+      return app.launch();
     } else {
-      return AppActorFront.reloadApp(client, actor, manifest);
+      return app.reload();
     }
   },
 
   installAndRunProject: function() {
     let project = this.selectedProject;
 
     if (!project || (project.type != "packaged" && project.type != "hosted")) {
       console.error("Can't install project. Unknown type of project.");
@@ -483,74 +442,67 @@ exports.AppManager = AppManager = {
 
       yield self.validateProject(project);
 
       if (project.errorsCount > 0) {
         self.reportError("error_cantInstallValidationErrors");
         return;
       }
 
-      let client = self.connection.client;
-      let actor = self._listTabsResponse.webappsActor;
       let installPromise;
 
       if (project.type != "packaged" && project.type != "hosted") {
         return promise.reject("Don't know how to install project");
       }
 
+      let response;
       if (project.type == "packaged") {
-        let {appId} = yield AppActorFront.installPackaged(client,
-                                                          actor,
-                                                          project.location,
-                                                          project.packagedAppOrigin);
+        response = yield self._appsFront.installPackaged(project.location,
+                                                             project.packagedAppOrigin);
+
         // If the packaged app specified a custom origin override,
         // we need to update the local project origin
-        project.packagedAppOrigin = appId;
+        project.packagedAppOrigin = response.appId;
         // And ensure the indexed db on disk is also updated
         AppProjects.update(project);
       }
 
       if (project.type == "hosted") {
         let manifestURLObject = Services.io.newURI(project.location, null, null);
         let origin = Services.io.newURI(manifestURLObject.prePath, null, null);
         let appId = origin.host;
         let metadata = {
           origin: origin.spec,
           manifestURL: project.location
         };
-        yield AppActorFront.installHosted(client,
-                                          actor,
-                                          appId,
-                                          metadata,
-                                          project.manifest);
+        response = yield self._appsFront.installHosted(appId,
+                                            metadata,
+                                            project.manifest);
       }
 
-      let manifest = self.getProjectManifestURL(project);
-      if (!self._runningApps.has(manifest)) {
+      let {app} = response;
+      if (!app.running) {
         let deferred = promise.defer();
         self.on("app-manager-update", function onUpdate(event, what) {
           if (what == "project-is-running") {
             self.off("app-manager-update", onUpdate);
             deferred.resolve();
           }
         });
-        yield AppActorFront.launchApp(client, actor, manifest);
+        yield app.launch();
         yield deferred.promise;
-
       } else {
-        yield AppActorFront.reloadApp(client, actor, manifest);
+        yield app.reload();
       }
     });
   },
 
   stopRunningApp: function() {
-    let client = this.connection.client;
-    let actor = this._listTabsResponse.webappsActor;
-    let manifest = this.getProjectManifestURL(this.selectedProject);
-    return AppActorFront.closeApp(client, actor, manifest);
+    let app = this._getProjectFront(this.selectedProject);
+    return app.close();
   },
 
   /* PROJECT VALIDATION */
 
   validateProject: function(project) {
     if (!project) {
       return promise.reject();
     }
--- a/browser/devtools/webide/test/test_basic.html
+++ b/browser/devtools/webide/test/test_basic.html
@@ -21,17 +21,16 @@
         Task.spawn(function* () {
             let win = yield openWebIDE();
 
             ok(win, "Found a window");
             ok(win.AppManager, "App Manager accessible");
             let appmgr = win.AppManager;
             ok(appmgr.connection, "App Manager connection ready");
             ok(appmgr.runtimeList, "Runtime list ready");
-            ok(appmgr.webAppsStore, "WebApps store ready");
 
             // test error reporting
             let nbox = win.document.querySelector("#notificationbox");
             let notification =  nbox.getNotificationWithValue("webide:errornotification");
             ok(!notification, "No notification yet");
             let deferred = promise.defer();
             nextTick().then(() => {
               deferred.reject("BOOM!");
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -1,18 +1,18 @@
 # -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 pref("devtools.webide.showProjectEditor", true);
 pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
 pref("devtools.webide.autoinstallADBHelper", true);
-pref("devtools.webide.lastprojectlocation", "");
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
 pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
 pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
 pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
 pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
 pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
 pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
 pref("devtools.webide.lastConnectedRuntime", "");
+pref("devtools.webide.lastSelectedProject", "");
--- a/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
+++ b/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
@@ -19,17 +19,19 @@
 
 'use strict';
 
 /*
  * pdfjschildbootstrap.js loads into the content process to take care of
  * initializing our built-in version of pdfjs when running remote.
  */
 
+Components.utils.import('resource://gre/modules/Services.jsm');
 Components.utils.import('resource://pdf.js/PdfJs.jsm');
 Components.utils.import('resource://pdf.js/PdfjsContentUtils.jsm');
 
 // init content utils shim pdfjs will use to access privileged apis.
 PdfjsContentUtils.init();
 
-// register various pdfjs factories that hook us into content loading.
-PdfJs.updateRegistration();
-
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+  // register various pdfjs factories that hook us into content loading.
+  PdfJs.updateRegistration();
+}
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
@@ -27,16 +27,20 @@ scratchpadContext.invalid=Scratchpad can
 # LOCALIZATION NOTE  (openFile.title): This is the file picker title, when you
 # open a file from Scratchpad.
 openFile.title=Open File
 
 # LOCALIZATION NOTE  (openFile.failed): This is the message displayed when file
 # open fails.
 openFile.failed=Failed to read the file.
 
+# LOCALIZATION NOTE  (openFile.failed): This is the message displayed when file
+# open fails.
+importFromFile.convert.failed=Failed to convert file to Unicode from %1$S.
+
 # LOCALIZATION NOTE (clearRecentMenuItems.label): This is the label for the
 # menuitem in the 'Open Recent'-menu which clears all recent files.
 clearRecentMenuItems.label=Clear Items
 
 # LOCALIZATION NOTE  (saveFileAs): This is the file picker title, when you save
 # a file in Scratchpad.
 saveFileAs=Save File As
 
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -280,17 +280,17 @@ PluginContent.prototype = {
     let eventType = event.type;
 
     if (eventType == "unload") {
       this.uninit();
       return;
     }
 
     if (eventType == "PluginRemoved") {
-      this.updateNotificationUI();
+      this.updateNotificationUI(event.target);
       return;
     }
 
     if (eventType == "click") {
       this.onOverlayClick(event);
       return;
     }
 
@@ -693,17 +693,39 @@ PluginContent.prototype = {
 
     this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", {
       plugins: [... this.pluginData.values()],
       showNow: showNow,
       host: principalHost,
     }, null, principal);
   },
 
-  updateNotificationUI: function () {
+  /**
+   * Updates the "hidden plugin" notification bar UI.
+   *
+   * @param document (optional)
+   *        Specify the document that is causing the update.
+   *        This is useful when the document is possibly no longer
+   *        the current loaded document (for example, if we're
+   *        responding to a PluginRemoved event for an unloading
+   *        document). If this parameter is omitted, it defaults
+   *        to the current top-level document.
+   */
+  updateNotificationUI: function (document) {
+    let principal;
+
+    if (document) {
+      // We're only interested in the top-level document, since that's
+      // the one that provides the Principal that we send back to the
+      // parent.
+      principal = document.defaultView.top.document.nodePrincipal;
+    } else {
+      principal = this.content.document.nodePrincipal;
+    }
+
     // Make a copy of the actions from the last popup notification.
     let haveInsecure = false;
     let actions = new Map();
     for (let action of this.pluginData.values()) {
       switch (action.fallbackType) {
         // haveInsecure will trigger the red flashing icon and the infobar
         // styling below
         case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
@@ -713,19 +735,18 @@ PluginContent.prototype = {
 
         case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
           actions.set(action.permissionString, action);
           continue;
       }
     }
 
     // Remove plugins that are already active, or large enough to show an overlay.
-    let contentWindow = this.global.content;
-    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
+    let cwu = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindowUtils);
     for (let plugin of cwu.plugins) {
       let info = this._getPluginInfo(plugin);
       if (!actions.has(info.permissionString)) {
         continue;
       }
       let fallbackType = info.fallbackType;
       if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
         actions.delete(info.permissionString);
@@ -750,17 +771,16 @@ PluginContent.prototype = {
         if (actions.size == 0) {
           break;
         }
       }
     }
 
     // If there are any items remaining in `actions` now, they are hidden
     // plugins that need a notification bar.
-    let principal = contentWindow.document.nodePrincipal;
     this.global.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", {
       haveInsecure: haveInsecure,
       actions: [... actions.values()],
       host: this._getHostFromPrincipal(principal),
     }, null, principal);
   },
 
   removeNotification: function (name) {
--- a/browser/themes/shared/newtab/controls.svg
+++ b/browser/themes/shared/newtab/controls.svg
@@ -1,28 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <svg version="1.1"
      id="icons-enhanced-tiles"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
      x="0"
      y="0"
-     width="256"
+     width="288"
      height="32"
-     viewBox="0 0 256 32">
+     viewBox="0 0 288 32">
 
   <defs>
     <style type="text/css"><![CDATA[
       /* Glyph Styles */
 
       .glyphShape-style {
         fill: #737373;
       }
 
+      .glyphShape-style-pin {
+        fill: #b4b4b4;
+      }
+
       .glyphShape-style-hover-gear {
         fill: #4a90e2;
       }
 
       .glyphShape-style-hover-pin {
         fill: #4a90e2;
       }
 
@@ -100,9 +104,13 @@
   </g>
 
   <g id="icon-delete-hover-active" transform="translate(224)">
     <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
     <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
     <use xlink:href="#glyphShape-delete" class="glyphShape-style-hover-active" />
   </g>
 
+  <g id="icon-pin-default" transform="translate(256)">
+    <use xlink:href="#glyphShape-pin"    class="glyphShape-style-pin" />
+  </g>
+
 </svg>
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -117,40 +117,55 @@
   background-origin: padding-box;
   background-clip: padding-box;
   background-repeat: no-repeat;
   background-size: cover;
   border-radius: inherit;
   transition: opacity 100ms ease-out;
 }
 
-.newtab-thumbnail.enhanced-content:hover {
+.newtab-site:hover .newtab-thumbnail.enhanced-content {
   opacity: 0;
 }
 
 .newtab-site[type=affiliate] .newtab-thumbnail,
 .newtab-site[type=enhanced] .newtab-thumbnail,
 .newtab-site[type=organic] .newtab-thumbnail,
 .newtab-site[type=sponsored] .newtab-thumbnail {
   background-position: center center;
   background-size: auto;
 }
 
 /* TITLES */
+.newtab-sponsored,
 .newtab-title {
-  color: #737373;
+  color: #5c5c5c;
 }
 
 .newtab-site:hover .newtab-title {
-  color: #4a4a4a;
+  color: #222;
 }
 
 .newtab-site[pinned] .newtab-title {
-  color: #2c72c4;
-  font-weight: bold;
+  padding: 0 15px;
+}
+
+.newtab-site[pinned] .newtab-title::before {
+  background-image: -moz-image-rect(url("chrome://browser/skin/newtab/controls.svg"), 7, 278, 28, 266);
+  background-size: 10px;
+  content: "";
+  height: 17px;
+  left: 0;
+  position: absolute;
+  width: 10px;
+}
+
+.newtab-site[pinned] .newtab-title:-moz-locale-dir(rtl)::before {
+  left: auto;
+  right: 0;
 }
 
 /* CONTROLS */
 .newtab-control {
   background-color: transparent;
   background-size: 24px;
   border: none;
   height: 24px;
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -443,16 +443,17 @@ skip-if = true # bug 1021673
 [test_seekLies.html]
 [test_source.html]
 [test_source_media.html]
 [test_source_null.html]
 [test_source_write.html]
 [test_standalone.html]
 [test_streams_autoplay.html]
 [test_streams_element_capture.html]
+skip-if = e10s && os == 'win' # Bug 1065881 - Crash on child process shutdown in ShadowLayerForwarder::InWorkerThread
 [test_streams_element_capture_createObjectURL.html]
 [test_streams_element_capture_playback.html]
 [test_streams_element_capture_reset.html]
 skip-if = buildapp == 'b2g' # bug 901102
 [test_streams_gc.html]
 skip-if = buildapp == 'b2g' # bug 1021682
 [test_streams_srcObject.html]
 [test_streams_tracks.html]
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3051,39 +3051,39 @@ nsDocShell::AddWeakScrollObserver(nsIScr
 NS_IMETHODIMP
 nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver)
 {
     nsWeakPtr obs = do_GetWeakReference(aObserver);
     return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 void
-nsDocShell::NotifyAsyncPanZoomStarted()
+nsDocShell::NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos)
 {
     nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
     while (iter.HasMore()) {
         nsWeakPtr ref = iter.GetNext();
         nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
         if (obs) {
-            obs->AsyncPanZoomStarted();
+            obs->AsyncPanZoomStarted(aScrollPos);
         } else {
             mScrollObservers.RemoveElement(ref);
         }
     }
 }
 
 void
-nsDocShell::NotifyAsyncPanZoomStopped()
+nsDocShell::NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos)
 {
     nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
     while (iter.HasMore()) {
         nsWeakPtr ref = iter.GetNext();
         nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
         if (obs) {
-            obs->AsyncPanZoomStopped();
+            obs->AsyncPanZoomStopped(aScrollPos);
         } else {
             mScrollObservers.RemoveElement(ref);
         }
     }
 }
 
 NS_IMETHODIMP
 nsDocShell::NotifyScrollObservers()
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -44,16 +44,17 @@
 #include "nsILoadContext.h"
 #include "nsIWebShellServices.h"
 #include "nsILinkHandler.h"
 #include "nsIClipboardCommands.h"
 #include "nsITabParent.h"
 #include "nsCRT.h"
 #include "prtime.h"
 #include "nsRect.h"
+#include "Units.h"
 
 namespace mozilla {
 namespace dom {
 class EventTarget;
 class URLSearchParams;
 }
 }
 
@@ -245,20 +246,20 @@ public:
         FireOnLocationChange(this, nullptr, mCurrentURI,
                              LOCATION_CHANGE_SAME_DOCUMENT);
     }
 
     nsresult HistoryTransactionRemoved(int32_t aIndex);
 
     // Notify Scroll observers when an async panning/zooming transform
     // has started being applied
-    void NotifyAsyncPanZoomStarted();
+    void NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos);
     // Notify Scroll observers when an async panning/zooming transform
     // is no longer applied
-    void NotifyAsyncPanZoomStopped();
+    void NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos);
 
     // Add new profile timeline markers to this docShell. This will only add
     // markers if the docShell is currently recording profile timeline markers.
     // See nsIDocShell::recordProfileTimelineMarkers
     void AddProfileTimelineMarker(const char* aName,
                                   TracingMetadata aMetaData);
     void AddProfileTimelineMarker(const char* aName,
                                   ProfilerBacktrace* aCause,
--- a/docshell/base/nsIScrollObserver.h
+++ b/docshell/base/nsIScrollObserver.h
@@ -2,37 +2,40 @@
 /* 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 nsIScrollObserver_h___
 #define nsIScrollObserver_h___
 
 #include "nsISupports.h"
+#include "Units.h"
 
 #define NS_ISCROLLOBSERVER_IID \
-  { 0x03465b77, 0x9ce2, 0x4d19, \
-    { 0xb2, 0xf6, 0x82, 0xae, 0xee, 0x85, 0xc3, 0xbf } }
+  { 0x00bc10e3, 0xaa59, 0x4aa3, \
+    { 0x88, 0xe9, 0x43, 0x0a, 0x01, 0xa3, 0x88, 0x04 } }
 
 class nsIScrollObserver : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
 
   /**
    * Called when the scroll position of some element has changed.
    */
   virtual void ScrollPositionChanged() = 0;
 
   /**
-   * Called when an async panning/zooming transform has started being applied.
+   * Called when an async panning/zooming transform has started being applied
+   * and passed the scroll offset
    */
-  virtual void AsyncPanZoomStarted(){};
+  virtual void AsyncPanZoomStarted(const mozilla::CSSIntPoint scrollPos){};
 
   /**
-   * Called when an async panning/zooming transform is no longer applied.
+   * Called when an async panning/zooming transform is no longer applied
+   * and passed the scroll offset
    */
-  virtual void AsyncPanZoomStopped(){};
+  virtual void AsyncPanZoomStopped(const mozilla::CSSIntPoint scrollPos){};
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID)
 
 #endif /* nsIScrollObserver_h___ */
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -15,16 +15,17 @@
 
 #include "mozilla/dom/ContentParent.h"
 
 #include "nsThreadUtils.h"
 #include "nsHashPropertyBag.h"
 #include "nsComponentManagerUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsJSUtils.h"
 #include "nsIAudioManager.h"
 #include "SpeakerManagerService.h"
 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
 #endif
 
@@ -796,58 +797,43 @@ AudioChannelService::Observe(nsISupports
     } else {
       NS_WARNING("ipc:content-shutdown message without childID property");
     }
   }
 #ifdef MOZ_WIDGET_GONK
   // To process the volume control on each audio channel according to
   // change of settings
   else if (!strcmp(aTopic, "mozsettings-changed")) {
-    AutoSafeJSContext cx;
-    nsDependentString dataStr(aData);
-    JS::Rooted<JS::Value> val(cx);
-    if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
-        !val.isObject()) {
-      return NS_OK;
-    }
-
-    JS::Rooted<JSObject*> obj(cx, &val.toObject());
-    JS::Rooted<JS::Value> key(cx);
-    if (!JS_GetProperty(cx, obj, "key", &key) ||
-        !key.isString()) {
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+    RootedDictionary<SettingChangeNotification> setting(cx);
+    if (!WrappedJSToDictionary(cx, aSubject, setting)) {
       return NS_OK;
     }
-
-    JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key));
-    if (!jsKey) {
+    if (!StringBeginsWith(setting.mKey, NS_LITERAL_STRING("audio.volume."))) {
       return NS_OK;
     }
-    nsAutoJSString keyStr;
-    if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) {
+    if (!setting.mValue.isNumber()) {
       return NS_OK;
     }
-
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) {
-      return NS_OK;
-    }
-
+    
     nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
     NS_ENSURE_TRUE(audioManager, NS_OK);
 
-    int32_t index = value.toInt32();
-    if (keyStr.EqualsLiteral("audio.volume.content")) {
+    int32_t index = setting.mValue.toNumber();
+    if (setting.mKey.EqualsLiteral("audio.volume.content")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index);
-    } else if (keyStr.EqualsLiteral("audio.volume.notification")) {
+    } else if (setting.mKey.EqualsLiteral("audio.volume.notification")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index);
-    } else if (keyStr.EqualsLiteral("audio.volume.alarm")) {
+    } else if (setting.mKey.EqualsLiteral("audio.volume.alarm")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index);
-    } else if (keyStr.EqualsLiteral("audio.volume.telephony")) {
+    } else if (setting.mKey.EqualsLiteral("audio.volume.telephony")) {
       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index);
-    } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) {
+    } else if (!setting.mKey.EqualsLiteral("audio.volume.bt_sco")) {
       // bt_sco is not a valid audio channel so we manipulate it in
       // AudioManager.cpp. And the others should not be used.
       // We didn't use MOZ_CRASH or MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE here
       // because any web content who has permission of mozSettings can set any
       // names then it can be easy to crash the B2G.
       NS_WARNING("unexpected audio channel for volume control");
     }
   }
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2972,35 +2972,31 @@ CallerSubsumes(JS::Handle<JS::Value> aVa
   if (!aValue.isObject()) {
     return true;
   }
   return CallerSubsumes(&aValue.toObject());
 }
 
 template<class T>
 inline bool
-WrappedJSToDictionary(nsISupports* aObject, T& aDictionary)
+WrappedJSToDictionary(JSContext* aCx, nsISupports* aObject, T& aDictionary)
 {
   nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(aObject);
   if (!wrappedObj) {
     return false;
   }
 
-  AutoJSAPI jsapi;
-  jsapi.Init();
-
-  JSContext* cx = jsapi.cx();
-  JS::Rooted<JSObject*> obj(cx, wrappedObj->GetJSObject());
+  JS::Rooted<JSObject*> obj(aCx, wrappedObj->GetJSObject());
   if (!obj) {
     return false;
   }
 
-  JSAutoCompartment ac(cx, obj);
-  JS::Rooted<JS::Value> v(cx, OBJECT_TO_JSVAL(obj));
-  return aDictionary.Init(cx, v);
+  JSAutoCompartment ac(aCx, obj);
+  JS::Rooted<JS::Value> v(aCx, JS::ObjectValue(*obj));
+  return aDictionary.Init(aCx, v);
 }
 
 
 template<class T, class S>
 inline nsRefPtr<T>
 StrongOrRawPtr(already_AddRefed<S>&& aPtr)
 {
   return aPtr.template downcast<T>();
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -29,16 +29,17 @@
 #include "mozilla/ipc/UnixSocket.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPCOM.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #if defined(MOZ_WIDGET_GONK)
 #include "cutils/properties.h"
 #endif
 
 #if defined(MOZ_B2G_BT)
 #if defined(MOZ_B2G_BT_BLUEZ)
 /**
@@ -555,101 +556,54 @@ BluetoothService::HandleStartup()
 nsresult
 BluetoothService::HandleStartupSettingsCheck(bool aEnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   return StartStopBluetooth(aEnable, true);
 }
 
 nsresult
-BluetoothService::HandleSettingsChanged(const nsAString& aData)
+BluetoothService::HandleSettingsChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"bluetooth.enabled","value":true}
 
-  AutoSafeJSContext cx;
-  if (!cx) {
-    return NS_OK;
-  }
-
-  JS::Rooted<JS::Value> val(cx);
-  if (!JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val)) {
-    return JS_ReportPendingException(cx) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!val.isObject()) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
     return NS_OK;
   }
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!key.isString()) {
-    return NS_OK;
-  }
-
-  // First, check if the string equals to BLUETOOTH_DEBUGGING_SETTING
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_DEBUGGING_SETTING, &match)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (match) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      MOZ_ASSERT(!JS_IsExceptionPending(cx));
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!value.isBoolean()) {
+  if (setting.mKey.EqualsASCII(BLUETOOTH_DEBUGGING_SETTING)) {
+    if (!setting.mValue.isBoolean()) {
       MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.debugging.enabled'!");
       return NS_ERROR_UNEXPECTED;
     }
-
-    SWITCH_BT_DEBUG(value.toBoolean());
+  
+    SWITCH_BT_DEBUG(setting.mValue.toBoolean());
 
     return NS_OK;
   }
 
   // Second, check if the string is BLUETOOTH_ENABLED_SETTING
-  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_ENABLED_SETTING, &match)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
+  if (!setting.mKey.EqualsASCII(BLUETOOTH_ENABLED_SETTING)) {
+    return NS_OK;
+  }
+  if (!setting.mValue.isBoolean()) {
+    MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
+    return NS_ERROR_UNEXPECTED;
   }
 
-  if (match) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      MOZ_ASSERT(!JS_IsExceptionPending(cx));
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
+  sToggleInProgress = true;
 
-    if (!value.isBoolean()) {
-      MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.enabled'!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    if (sToggleInProgress || value.toBoolean() == IsEnabled()) {
-      // Nothing to do here.
-      return NS_OK;
-    }
-
-    sToggleInProgress = true;
-
-    nsresult rv = StartStopBluetooth(value.toBoolean(), false);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  nsresult rv = StartStopBluetooth(setting.mValue.toBoolean(), false);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 BluetoothService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -750,17 +704,17 @@ BluetoothService::Observe(nsISupports* a
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!strcmp(aTopic, "profile-after-change")) {
     return HandleStartup();
   }
 
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    return HandleSettingsChanged(nsDependentString(aData));
+    return HandleSettingsChanged(aSubject);
   }
 
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     return HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothService got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -367,17 +367,17 @@ protected:
    */
   nsresult
   HandleStartupSettingsCheck(bool aEnable);
 
   /**
    * Called when "mozsettings-changed" observer topic fires.
    */
   nsresult
-  HandleSettingsChanged(const nsAString& aData);
+  HandleSettingsChanged(nsISupports* aSubject);
 
   /**
    * Called when XPCOM is shutting down.
    */
   virtual nsresult
   HandleShutdown();
 
   // Called by ToggleBtAck.
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -22,16 +22,18 @@
 #include "nsIMobileConnectionService.h"
 #include "nsIMobileNetworkInfo.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsITelephonyService.h"
 #include "nsRadioInterfaceLayer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #define MOZSETTINGS_CHANGED_ID               "mozsettings-changed"
 #define AUDIO_VOLUME_BT_SCO_ID               "audio.volume.bt_sco"
 
 /**
  * Dispatch task with arguments to main thread.
  */
 using namespace mozilla;
@@ -457,17 +459,17 @@ BluetoothHfpManager::Get()
 }
 
 NS_IMETHODIMP
 BluetoothHfpManager::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    HandleVolumeChanged(nsDependentString(aData));
+    HandleVolumeChanged(aSubject);
   } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     HandleShutdown();
   } else {
     MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!");
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
@@ -556,49 +558,38 @@ public:
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     BT_WARNING("BluetoothHandsfreeInterface::VolumeControl failed: %d",
                (int)aStatus);
   }
 };
 
 void
-BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
+BluetoothHfpManager::HandleVolumeChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
-  JSContext* cx = nsContentUtils::GetSafeJSContext();
-  NS_ENSURE_TRUE_VOID(cx);
-
-  JS::Rooted<JS::Value> val(cx);
-  NS_ENSURE_TRUE_VOID(JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val));
-  NS_ENSURE_TRUE_VOID(val.isObject());
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<dom::SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+    return;
+  }
+  if (!setting.mKey.EqualsASCII(AUDIO_VOLUME_BT_SCO_ID)) {
+    return;
+  }
+  if (!setting.mValue.isNumber()) {
     return;
   }
 
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_BT_SCO_ID, &match) ||
-      !match) {
-    return;
-  }
-
-  JS::Rooted<JS::Value> value(cx);
-  if (!JS_GetProperty(cx, obj, "value", &value) ||
-      !value.isNumber()) {
-    return;
-  }
-
-  mCurrentVgs = value.toNumber();
+  mCurrentVgs = setting.mValue.toNumber();
 
   // Adjust volume by headset and we don't have to send volume back to headset
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return;
   }
 
   // Only send volume back when there's a connected headset
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
@@ -144,17 +144,17 @@ private:
   friend class RespondToBLDNTask;
   friend class MainThreadTask;
 
   BluetoothHfpManager();
   bool Init();
   void Cleanup();
 
   void HandleShutdown();
-  void HandleVolumeChanged(const nsAString& aData);
+  void HandleVolumeChanged(nsISupports* aSubject);
   void Notify(const hal::BatteryInformation& aBatteryInfo);
 
   void NotifyConnectionStateChanged(const nsAString& aType);
   void NotifyDialer(const nsAString& aCommand);
 
   PhoneType GetPhoneType(const nsAString& aType);
   void ResetCallArray();
   uint32_t FindFirstCall(uint16_t aState);
--- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp
@@ -18,16 +18,18 @@
 #include "jsapi.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsServiceManagerUtils.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #ifdef MOZ_B2G_RIL
 #include "nsIDOMIccInfo.h"
 #include "nsIIccProvider.h"
 #include "nsIMobileConnectionInfo.h"
 #include "nsIMobileConnectionService.h"
 #include "nsIMobileNetworkInfo.h"
 #include "nsITelephonyService.h"
@@ -200,17 +202,17 @@ NS_IMPL_ISUPPORTS(BluetoothHfpManager::G
                   nsISettingsServiceCallback);
 
 NS_IMETHODIMP
 BluetoothHfpManager::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    HandleVolumeChanged(nsDependentString(aData));
+    HandleVolumeChanged(aSubject);
   } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     HandleShutdown();
   } else {
     MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!");
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
@@ -550,50 +552,39 @@ BluetoothHfpManager::NotifyDialer(const 
 
   if (!BroadcastSystemMessage(type, parameters)) {
     BT_WARNING("Failed to broadcast system message to dialer");
   }
 }
 #endif // MOZ_B2G_RIL
 
 void
-BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
+BluetoothHfpManager::HandleVolumeChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
 
-  JSContext* cx = nsContentUtils::GetSafeJSContext();
-  NS_ENSURE_TRUE_VOID(cx);
-
-  JS::Rooted<JS::Value> val(cx);
-  NS_ENSURE_TRUE_VOID(JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val));
-  NS_ENSURE_TRUE_VOID(val.isObject());
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+    return;
+  }
+  if (!setting.mKey.EqualsASCII(AUDIO_VOLUME_BT_SCO_ID)) {
+    return;
+  }
+  if (!setting.mValue.isNumber()) {
     return;
   }
 
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_BT_SCO_ID, &match) ||
-      !match) {
-    return;
-  }
-
-  JS::Rooted<JS::Value> value(cx);
-  if (!JS_GetProperty(cx, obj, "value", &value)||
-      !value.isNumber()) {
-    return;
-  }
-
-  mCurrentVgs = value.toNumber();
+  mCurrentVgs = setting.mValue.toNumber();
 
   // Adjust volume by headset and we don't have to send volume back to headset
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return;
   }
 
   // Only send volume back when there's a connected headset
--- a/dom/bluetooth/bluez/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.h
@@ -144,17 +144,17 @@ private:
 #ifdef MOZ_B2G_RIL
   friend class RespondToBLDNTask;
   friend class SendRingIndicatorTask;
 #endif
   friend class BluetoothHfpManagerObserver;
 
   BluetoothHfpManager();
   void HandleShutdown();
-  void HandleVolumeChanged(const nsAString& aData);
+  void HandleVolumeChanged(nsISupports* aSubject);
 
   bool Init();
   void Notify(const hal::BatteryInformation& aBatteryInfo);
 #ifdef MOZ_B2G_RIL
   void ResetCallArray();
   uint32_t FindFirstCall(uint16_t aState);
   uint32_t GetNumberOfCalls(uint16_t aState);
   uint32_t GetNumberOfConCalls();
--- a/dom/bluetooth2/BluetoothService.cpp
+++ b/dom/bluetooth2/BluetoothService.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPCOM.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #if defined(MOZ_WIDGET_GONK)
 #include "cutils/properties.h"
 #endif
 
 #if defined(MOZ_B2G_BT)
 #if defined(MOZ_B2G_BT_BLUEZ)
 /**
@@ -517,70 +518,39 @@ BluetoothService::HandleStartup()
 nsresult
 BluetoothService::HandleStartupSettingsCheck(bool aEnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   return StartStopBluetooth(aEnable, true, nullptr);
 }
 
 nsresult
-BluetoothService::HandleSettingsChanged(const nsAString& aData)
+BluetoothService::HandleSettingsChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"bluetooth.enabled","value":true}
 
-  AutoSafeJSContext cx;
-  if (!cx) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
     return NS_OK;
   }
-
-  JS::Rooted<JS::Value> val(cx);
-  if (!JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val)) {
-    return JS_ReportPendingException(cx) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (!val.isObject()) {
+  if (!setting.mKey.EqualsASCII(BLUETOOTH_DEBUGGING_SETTING)) {
     return NS_OK;
   }
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
+  if (!setting.mValue.isBoolean()) {
+    MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.debugging.enabled'!");
+    return NS_ERROR_UNEXPECTED;
   }
 
-  if (!key.isString()) {
-    return NS_OK;
-  }
-
-  // Check whether the string is BLUETOOTH_DEBUGGING_SETTING
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), BLUETOOTH_DEBUGGING_SETTING, &match)) {
-    MOZ_ASSERT(!JS_IsExceptionPending(cx));
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (match) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      MOZ_ASSERT(!JS_IsExceptionPending(cx));
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!value.isBoolean()) {
-      MOZ_ASSERT(false, "Expecting a boolean for 'bluetooth.debugging.enabled'!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    SWITCH_BT_DEBUG(value.toBoolean());
-  }
+  SWITCH_BT_DEBUG(setting.mValue.toBoolean());
 
   return NS_OK;
 }
 
 nsresult
 BluetoothService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -681,17 +651,17 @@ BluetoothService::Observe(nsISupports* a
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!strcmp(aTopic, "profile-after-change")) {
     return HandleStartup();
   }
 
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    return HandleSettingsChanged(nsDependentString(aData));
+    return HandleSettingsChanged(aSubject);
   }
 
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     return HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothService got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -366,17 +366,17 @@ protected:
    */
   nsresult
   HandleStartupSettingsCheck(bool aEnable);
 
   /**
    * Called when "mozsettings-changed" observer topic fires.
    */
   nsresult
-  HandleSettingsChanged(const nsAString& aData);
+  HandleSettingsChanged(nsISupports* aSubject);
 
   /**
    * Called when XPCOM is shutting down.
    */
   virtual nsresult
   HandleShutdown();
 
   // Called by ToggleBtAck.
--- a/dom/bluetooth2/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth2/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -22,16 +22,17 @@
 #include "nsIMobileConnectionService.h"
 #include "nsIMobileNetworkInfo.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsITelephonyService.h"
 #include "nsRadioInterfaceLayer.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #define MOZSETTINGS_CHANGED_ID               "mozsettings-changed"
 #define AUDIO_VOLUME_BT_SCO_ID               "audio.volume.bt_sco"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
@@ -460,17 +461,17 @@ BluetoothHfpManager::Get()
 }
 
 NS_IMETHODIMP
 BluetoothHfpManager::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    HandleVolumeChanged(nsDependentString(aData));
+    HandleVolumeChanged(aSubject);
   } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     HandleShutdown();
   } else {
     MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!");
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
@@ -559,49 +560,39 @@ public:
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     BT_WARNING("BluetoothHandsfreeInterface::VolumeControl failed: %d",
                (int)aStatus);
   }
 };
 
 void
-BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
+BluetoothHfpManager::HandleVolumeChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
-  JSContext* cx = nsContentUtils::GetSafeJSContext();
-  NS_ENSURE_TRUE_VOID(cx);
 
-  JS::Rooted<JS::Value> val(cx);
-  NS_ENSURE_TRUE_VOID(JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val));
-  NS_ENSURE_TRUE_VOID(val.isObject());
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<dom::SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+    return;
+  }
+  if (!setting.mKey.EqualsASCII(AUDIO_VOLUME_BT_SCO_ID)) {
+    return;
+  }
+  if (!setting.mValue.isNumber()) {
     return;
   }
 
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_BT_SCO_ID, &match) ||
-      !match) {
-    return;
-  }
-
-  JS::Rooted<JS::Value> value(cx);
-  if (!JS_GetProperty(cx, obj, "value", &value) ||
-      !value.isNumber()) {
-    return;
-  }
-
-  mCurrentVgs = value.toNumber();
+  mCurrentVgs = setting.mValue.toNumber();
 
   // Adjust volume by headset and we don't have to send volume back to headset
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return;
   }
 
   // Only send volume back when there's a connected headset
--- a/dom/bluetooth2/bluedroid/hfp/BluetoothHfpManager.h
+++ b/dom/bluetooth2/bluedroid/hfp/BluetoothHfpManager.h
@@ -142,17 +142,17 @@ private:
   friend class CloseScoTask;
   friend class RespondToBLDNTask;
   friend class MainThreadTask;
 
   BluetoothHfpManager();
   bool Init();
 
   void HandleShutdown();
-  void HandleVolumeChanged(const nsAString& aData);
+  void HandleVolumeChanged(nsISupports* aSubject);
   void Notify(const hal::BatteryInformation& aBatteryInfo);
 
   void NotifyConnectionStateChanged(const nsAString& aType);
   void NotifyDialer(const nsAString& aCommand);
 
   PhoneType GetPhoneType(const nsAString& aType);
   void ResetCallArray();
   uint32_t FindFirstCall(uint16_t aState);
--- a/dom/bluetooth2/bluez/BluetoothHfpManager.cpp
+++ b/dom/bluetooth2/bluez/BluetoothHfpManager.cpp
@@ -18,16 +18,17 @@
 #include "jsapi.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsServiceManagerUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #ifdef MOZ_B2G_RIL
 #include "nsIDOMIccInfo.h"
 #include "nsIIccProvider.h"
 #include "nsIMobileConnectionInfo.h"
 #include "nsIMobileConnectionService.h"
 #include "nsIMobileNetworkInfo.h"
 #include "nsITelephonyService.h"
@@ -200,17 +201,17 @@ NS_IMPL_ISUPPORTS(BluetoothHfpManager::G
                   nsISettingsServiceCallback);
 
 NS_IMETHODIMP
 BluetoothHfpManager::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
   if (!strcmp(aTopic, MOZSETTINGS_CHANGED_ID)) {
-    HandleVolumeChanged(nsDependentString(aData));
+    HandleVolumeChanged(aSubject);
   } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     HandleShutdown();
   } else {
     MOZ_ASSERT(false, "BluetoothHfpManager got unexpected topic!");
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
@@ -550,50 +551,39 @@ BluetoothHfpManager::NotifyDialer(const 
 
   if (!BroadcastSystemMessage(type, parameters)) {
     BT_WARNING("Failed to broadcast system message to dialer");
   }
 }
 #endif // MOZ_B2G_RIL
 
 void
-BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
+BluetoothHfpManager::HandleVolumeChanged(nsISupports* aSubject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
 
-  JSContext* cx = nsContentUtils::GetSafeJSContext();
-  NS_ENSURE_TRUE_VOID(cx);
-
-  JS::Rooted<JS::Value> val(cx);
-  NS_ENSURE_TRUE_VOID(JS_ParseJSON(cx, aData.BeginReading(), aData.Length(), &val));
-  NS_ENSURE_TRUE_VOID(val.isObject());
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<dom::SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+    return;
+  }
+  if (!setting.mKey.EqualsASCII(AUDIO_VOLUME_BT_SCO_ID)) {
+    return;
+  }
+  if (!setting.mValue.isNumber()) {
     return;
   }
 
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), AUDIO_VOLUME_BT_SCO_ID, &match) ||
-      !match) {
-    return;
-  }
-
-  JS::Rooted<JS::Value> value(cx);
-  if (!JS_GetProperty(cx, obj, "value", &value)||
-      !value.isNumber()) {
-    return;
-  }
-
-  mCurrentVgs = value.toNumber();
+  mCurrentVgs = setting.mValue.toNumber();
 
   // Adjust volume by headset and we don't have to send volume back to headset
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return;
   }
 
   // Only send volume back when there's a connected headset
--- a/dom/bluetooth2/bluez/BluetoothHfpManager.h
+++ b/dom/bluetooth2/bluez/BluetoothHfpManager.h
@@ -144,17 +144,17 @@ private:
 #ifdef MOZ_B2G_RIL
   friend class RespondToBLDNTask;
   friend class SendRingIndicatorTask;
 #endif
   friend class BluetoothHfpManagerObserver;
 
   BluetoothHfpManager();
   void HandleShutdown();
-  void HandleVolumeChanged(const nsAString& aData);
+  void HandleVolumeChanged(nsISupports* aSubject);
 
   bool Init();
   void Notify(const hal::BatteryInformation& aBatteryInfo);
 #ifdef MOZ_B2G_RIL
   void ResetCallArray();
   uint32_t FindFirstCall(uint16_t aState);
   uint32_t GetNumberOfCalls(uint16_t aState);
   uint32_t GetNumberOfConCalls();
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -216,16 +216,21 @@ BrowserElementChild.prototype = {
                      /* useCapture = */ true,
                      /* wantsUntrusted = */ false);
 
     addEventListener('mozselectionchange',
                      this._selectionChangeHandler.bind(this),
                      /* useCapture = */ false,
                      /* wantsUntrusted = */ false);
 
+    addEventListener('scrollviewchange',
+                     this._ScrollViewChangeHandler.bind(this),
+                     /* useCapture = */ false,
+                     /* wantsUntrusted = */ false);
+
     // This listens to unload events from our message manager, but /not/ from
     // the |content| window.  That's because the window's unload event doesn't
     // bubble, and we're not using a capturing listener.  If we'd used
     // useCapture == true, we /would/ hear unload events from the window, which
     // is not what we want!
     addEventListener('unload',
                      this._unloadHandler.bind(this),
                      /* useCapture = */ false,
@@ -621,16 +626,26 @@ BrowserElementChild.prototype = {
 
     if (lang) {
       meta.lang = lang;
     }
 
     sendAsyncMsg('metachange', meta);
   },
 
+  _ScrollViewChangeHandler: function(e) {
+    e.stopPropagation();
+    let detail = {
+      state: e.state,
+      scrollX: e.scrollX,
+      scrollY: e.scrollY,
+    };
+    sendAsyncMsg('scrollviewchange', detail);
+  },
+
   _selectionChangeHandler: function(e) {
     e.stopPropagation();
     let boundingClientRect = e.boundingClientRect;
     if (!boundingClientRect) {
       return;
     }
 
     let zoomFactor = content.screen.width / content.innerWidth;
@@ -661,17 +676,17 @@ BrowserElementChild.prototype = {
       let currentRect = currentWindow.frameElement.getBoundingClientRect();
       detail.rect.top += currentRect.top;
       detail.rect.bottom += currentRect.top;
       detail.rect.left += currentRect.left;
       detail.rect.right += currentRect.left;
       currentWindow = currentWindow.parent;
     }
 
-    sendAsyncMsg("selectionchange", detail);
+    sendAsyncMsg('selectionchange', detail);
   },
 
   _themeColorChangedHandler: function(eventType, target) {
     let meta = {
       name: 'theme-color',
       content: target.content,
       type: eventType.replace('DOMMeta', '').toLowerCase()
     };
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -250,17 +250,18 @@ BrowserElementParent.prototype = {
       "got-can-go-back": this._gotDOMRequestResult,
       "got-can-go-forward": this._gotDOMRequestResult,
       "fullscreen-origin-change": this._remoteFullscreenOriginChange,
       "rollback-fullscreen": this._remoteFrameFullscreenReverted,
       "exit-fullscreen": this._exitFullscreen,
       "got-visible": this._gotDOMRequestResult,
       "visibilitychange": this._childVisibilityChange,
       "got-set-input-method-active": this._gotDOMRequestResult,
-      "selectionchange": this._handleSelectionChange
+      "selectionchange": this._handleSelectionChange,
+      "scrollviewchange": this._handleScrollViewChange
     };
 
     let mmSecuritySensitiveCalls = {
       "showmodalprompt": this._handleShowModalPrompt,
       "contextmenu": this._fireCtxMenuEvent,
       "securitychange": this._fireEventFromMsg,
       "locationchange": this._fireEventFromMsg,
       "iconchange": this._fireEventFromMsg,
@@ -491,16 +492,22 @@ BrowserElementParent.prototype = {
   },
 
   _handleSelectionChange: function(data) {
     let evt = this._createEvent('selectionchange', data.json,
                                 /* cancelable = */ false);
     this._frameElement.dispatchEvent(evt);
   },
 
+  _handleScrollViewChange: function(data) {
+    let evt = this._createEvent("scrollviewchange", data.json,
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
+  },
+
   _createEvent: function(evtName, detail, cancelable) {
     // This will have to change if we ever want to send a CustomEvent with null
     // detail.  For now, it's OK.
     if (detail !== undefined && detail !== null) {
       detail = Cu.cloneInto(detail, this._window);
       return new this._window.CustomEvent('mozbrowser' + evtName,
                                           { bubbles: true,
                                             cancelable: cancelable,
--- a/dom/fmradio/FMRadioService.cpp
+++ b/dom/fmradio/FMRadioService.cpp
@@ -11,16 +11,18 @@
 #include "AudioManager.h"
 #include "nsDOMClassInfo.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/FMRadioChild.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsJSUtils.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #define BAND_87500_108000_kHz 1
 #define BAND_76000_108000_kHz 2
 #define BAND_76000_90000_kHz  3
 
 #define MOZSETTINGS_CHANGED_ID "mozsettings-changed"
 #define SETTING_KEY_AIRPLANEMODE_ENABLED "airplaneMode.enabled"
 
@@ -673,70 +675,49 @@ FMRadioService::CancelSeek(FMRadioReplyR
   TransitionState(
     ErrorResponse(NS_LITERAL_STRING("Seek action is cancelled")), Enabled);
 
   aReplyRunnable->SetReply(SuccessResponse());
   NS_DispatchToMainThread(aReplyRunnable);
 }
 
 NS_IMETHODIMP
-FMRadioService::Observe(nsISupports * aSubject,
-                        const char * aTopic,
-                        const char16_t * aData)
+FMRadioService::Observe(nsISupports* aSubject,
+                        const char* aTopic,
+                        const char16_t* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sFMRadioService);
 
   if (strcmp(aTopic, MOZSETTINGS_CHANGED_ID) != 0) {
     return NS_OK;
   }
 
   // The string that we're interested in will be a JSON string looks like:
   //  {"key":"airplaneMode.enabled","value":true}
-  AutoSafeJSContext cx;
-  const nsDependentString dataStr(aData);
-  JS::Rooted<JS::Value> val(cx);
-  if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
-      !val.isObject()) {
-    NS_WARNING("Bad JSON string format.");
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<dom::SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
     return NS_OK;
   }
-
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) ||
-      !key.isString()) {
-    NS_WARNING("Failed to get string property `key`.");
+  if (!setting.mKey.EqualsASCII(SETTING_KEY_AIRPLANEMODE_ENABLED)) {
+    return NS_OK;
+  }
+  if (!setting.mValue.isBoolean()) {
     return NS_OK;
   }
 
-  JS::Rooted<JSString*> jsKey(cx, key.toString());
-  nsAutoJSString keyStr;
-  if (!keyStr.init(cx, jsKey)) {
-    return NS_OK;
-  }
+  mAirplaneModeEnabled = setting.mValue.toBoolean();
+  mHasReadAirplaneModeSetting = true;
 
-  if (keyStr.EqualsLiteral(SETTING_KEY_AIRPLANEMODE_ENABLED)) {
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value)) {
-      NS_WARNING("Failed to get property `value`.");
-      return NS_OK;
-    }
-
-    if (!value.isBoolean()) {
-      return NS_OK;
-    }
-
-    mAirplaneModeEnabled = value.toBoolean();
-    mHasReadAirplaneModeSetting = true;
-
-    // Disable the FM radio HW if Airplane mode is enabled.
-    if (mAirplaneModeEnabled) {
-      Disable(nullptr);
-    }
+  // Disable the FM radio HW if Airplane mode is enabled.
+  if (mAirplaneModeEnabled) {
+    Disable(nullptr);
   }
 
   return NS_OK;
 }
 
 void
 FMRadioService::NotifyFMRadioEvent(FMRadioEventType aType)
 {
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -19,16 +19,17 @@
 #include "nsIObserverService.h"
 #include "nsPIDOMWindow.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 class nsIPrincipal;
 
 #ifdef MOZ_ENABLE_QT5GEOPOSITION
 #include "QTMLocationProvider.h"
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
@@ -707,46 +708,36 @@ nsresult nsGeolocationService::Init()
   return NS_OK;
 }
 
 nsGeolocationService::~nsGeolocationService()
 {
 }
 
 void
-nsGeolocationService::HandleMozsettingChanged(const char16_t* aData)
+nsGeolocationService::HandleMozsettingChanged(nsISupports* aSubject)
 {
     // The string that we're interested in will be a JSON string that looks like:
     //  {"key":"gelocation.enabled","value":true}
 
-    AutoSafeJSContext cx;
-
-    nsDependentString dataStr(aData);
-    JS::Rooted<JS::Value> val(cx);
-    if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || !val.isObject()) {
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+    RootedDictionary<SettingChangeNotification> setting(cx);
+    if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+      return;
+    }
+    if (!setting.mKey.EqualsASCII(GEO_SETINGS_ENABLED)) {
+      return;
+    }
+    if (!setting.mValue.isBoolean()) {
       return;
     }
 
-    JS::Rooted<JSObject*> obj(cx, &val.toObject());
-    JS::Rooted<JS::Value> key(cx);
-    if (!JS_GetProperty(cx, obj, "key", &key) || !key.isString()) {
-      return;
-    }
-
-    bool match;
-    if (!JS_StringEqualsAscii(cx, key.toString(), GEO_SETINGS_ENABLED, &match) || !match) {
-      return;
-    }
-
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value) || !value.isBoolean()) {
-      return;
-    }
-
-    HandleMozsettingValue(value.toBoolean());
+    HandleMozsettingValue(setting.mValue.toBoolean());
 }
 
 void
 nsGeolocationService::HandleMozsettingValue(const bool aValue)
 {
     if (!aValue) {
       // turn things off
       StopDevice();
@@ -781,17 +772,17 @@ nsGeolocationService::Observe(nsISupport
       mGeolocators[i]->Shutdown();
     }
     StopDevice();
 
     return NS_OK;
   }
 
   if (!strcmp("mozsettings-changed", aTopic)) {
-    HandleMozsettingChanged(aData);
+    HandleMozsettingChanged(aSubject);
     return NS_OK;
   }
 
   if (!strcmp("timer-callback", aTopic)) {
     // decide if we can close down the service.
     for (uint32_t i = 0; i< mGeolocators.Length(); i++)
       if (mGeolocators[i]->HasActiveCallbacks()) {
         SetDisconnectTimer();
--- a/dom/geolocation/nsGeolocation.h
+++ b/dom/geolocation/nsGeolocation.h
@@ -65,17 +65,17 @@ public:
   NS_DECL_NSIOBSERVER
 
   nsGeolocationService() {
       mHigherAccuracy = false;
   }
 
   nsresult Init();
 
-  void HandleMozsettingChanged(const char16_t* aData);
+  void HandleMozsettingChanged(nsISupports* aSubject);
   void HandleMozsettingValue(const bool aValue);
 
   // Management of the Geolocation objects
   void AddLocator(mozilla::dom::Geolocation* locator);
   void RemoveLocator(mozilla::dom::Geolocation* locator);
 
   void SetCachedPosition(nsIDOMGeoPosition* aPosition);
   CachedPositionAndAccuracy GetCachedPosition();
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1995,37 +1995,37 @@ TabChild::RecvNotifyAPZStateChange(const
     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStarted();
     }
 
     nsCOMPtr<nsIDocument> doc = GetDocument();
     if (doc) {
       nsCOMPtr<nsIDocShell> docshell(doc->GetDocShell());
-      if (docshell) {
+      if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
-        nsdocshell->NotifyAsyncPanZoomStarted();
+        nsdocshell->NotifyAsyncPanZoomStarted(sf->GetScrollPositionCSSPixels());
       }
     }
     break;
   }
   case APZStateChange::TransformEnd:
   {
     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
     nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
     if (scrollbarMediator) {
       scrollbarMediator->ScrollbarActivityStopped();
     }
 
     nsCOMPtr<nsIDocument> doc = GetDocument();
     if (doc) {
       nsCOMPtr<nsIDocShell> docshell(doc->GetDocShell());
-      if (docshell) {
+      if (docshell && sf) {
         nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
-        nsdocshell->NotifyAsyncPanZoomStopped();
+        nsdocshell->NotifyAsyncPanZoomStopped(sf->GetScrollPositionCSSPixels());
       }
     }
     break;
   }
   case APZStateChange::StartTouch:
   {
     mActiveElementManager->HandleTouchStart(aArg);
     break;
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -183,46 +183,44 @@ XPCOMUtils.defineLazyGetter(this, "gMess
 
       target.sendAsyncMessage("NFC:PeerEvent", {
         event: event,
         sessionToken: sessionToken
       });
     },
 
     checkP2PRegistration: function checkP2PRegistration(message) {
-      // Check if the session and application id yeild a valid registered
-      // target.  It should have registered for NFC_PEER_EVENT_READY
-      let isValid = (this.nfc._currentSessionId != null) &&
-                    (this.peerTargets[message.data.appId] != null);
-
+      let target = this.peerTargets[message.data.appId];
+      let sessionToken = SessionHelper.getCurrentP2PToken();
+      let isValid = (sessionToken != null) && (target != null);
       let respMsg = { requestId: message.data.requestId };
       if (!isValid) {
         respMsg.errorMsg = this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_P2P_REG_INVALID);
       }
       // Notify the content process immediately of the status
       message.target.sendAsyncMessage(message.name + "Response", respMsg);
     },
 
     notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId) {
       let target = this.peerTargets[appId];
-      let sessionToken = this.nfc.sessionTokenMap[this.nfc._currentSessionId];
+      let sessionToken = SessionHelper.getCurrentP2PToken();
       let isValid = (sessionToken != null) && (target != null);
       if (!isValid) {
         debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
         return;
       }
 
       // Remember the target that receives onpeerready.
       this.currentPeer = target;
       this.notifyPeerEvent(target, NFC.NFC_PEER_EVENT_READY, sessionToken);
     },
 
     onPeerLost: function onPeerLost(sessionToken) {
       if (!this.currentPeer) {
-        // not a P2P session or the target is already killed.
+        // The target is already killed.
         return;
       }
 
       // For peerlost, the message is delievered to the target which
       // onpeerready has been called before.
       this.notifyPeerEvent(this.currentPeer, NFC.NFC_PEER_EVENT_LOST, sessionToken);
       this.currentPeer = null;
     },
@@ -262,19 +260,18 @@ XPCOMUtils.defineLazyGetter(this, "gMess
         }
       } else {
         debug("Ignoring unknown message type: " + message.name);
         return null;
       }
 
       switch (message.name) {
         case "NFC:CheckSessionToken":
-          if (message.data.sessionToken !== this.nfc.sessionTokenMap[this.nfc._currentSessionId]) {
-            debug("Received invalid Session Token: " + message.data.sessionToken +
-                  ", current SessionToken: " + this.nfc.sessionTokenMap[this.nfc._currentSessionId]);
+          if (!SessionHelper.isValidToken(message.data.sessionToken)) {
+            debug("Received invalid Session Token: " + message.data.sessionToken);
             return NFC.NFC_ERROR_BAD_SESSION_ID;
           }
           return NFC.NFC_SUCCESS;
         case "NFC:RegisterPeerReadyTarget":
           this.registerPeerReadyTarget(message.target, message.data.appId);
           return null;
         case "NFC:UnregisterPeerReadyTarget":
           this.unregisterPeerReadyTarget(message.data.appId);
@@ -309,47 +306,107 @@ XPCOMUtils.defineLazyGetter(this, "gMess
         case NFC.TOPIC_XPCOM_SHUTDOWN:
           this._shutdown();
           break;
       }
     },
   };
 });
 
+let SessionHelper = {
+  tokenMap: {},
+
+  registerSession: function registerSession(id, techList) {
+    if (this.tokenMap[id]) {
+      return this.tokenMap[id].token;
+    }
+
+    this.tokenMap[id] = {
+      token: UUIDGenerator.generateUUID().toString(),
+      isP2P: techList.indexOf("P2P") != -1
+    };
+
+    return this.tokenMap[id].token;
+  },
+
+  unregisterSession: function unregisterSession(id) {
+    if (this.tokenMap[id]) {
+      delete this.tokenMap[id];
+    }
+  },
+
+  getToken: function getToken(id) {
+    return this.tokenMap[id] ? this.tokenMap[id].token : null;
+  },
+
+  getCurrentP2PToken: function getCurrentP2PToken() {
+    for (let id in this.tokenMap) {
+      if (this.tokenMap[id] && this.tokenMap[id].isP2P) {
+        return this.tokenMap[id].token;
+      }
+    }
+    return null;
+  },
+
+  getId: function getId(token) {
+    for (let id in this.tokenMap) {
+      if (this.tokenMap[id].token == token) {
+        return id;
+      }
+    }
+
+    return 0;
+  },
+
+  isP2PSession: function isP2PSession(id) {
+    return (this.tokenMap[id] != null) && this.tokenMap[id].isP2P;
+  },
+
+  isValidToken: function isValidToken(token) {
+    for (let id in this.tokenMap) {
+      if (this.tokenMap[id].token == token) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+};
+
 function Nfc() {
   debug("Starting Nfc Service");
 
   let nfcService = Cc["@mozilla.org/nfc/service;1"].getService(Ci.nsINfcService);
   if (!nfcService) {
     debug("No nfc service component available!");
     return;
   }
 
   nfcService.start(this);
   this.nfcService = nfcService;
 
   gMessageManager.init(this);
 
-  // Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
-  this.sessionTokenMap = {};
   this.targetsByRequestId = {};
 }
 
 Nfc.prototype = {
 
   classID:   NFC_CID,
   classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
                                     classDescription: "Nfc",
                                     interfaces: [Ci.nsINfcService]}),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsINfcEventListener]),
 
-  _currentSessionId: null,
+  powerLevel: NFC.NFC_POWER_LEVEL_UNKNOWN,
 
-  powerLevel: NFC.NFC_POWER_LEVEL_UNKNOWN,
+  nfcService: null,
+
+  targetsByRequestId: null,
 
   /**
    * Send arbitrary message to Nfc service.
    *
    * @param nfcMessageType
    *        A text message type.
    * @param message [optional]
    *        An optional message object to send.
@@ -407,44 +464,38 @@ Nfc.prototype = {
     }
 
     switch (message.type) {
       case "InitializedNotification":
         // Do nothing.
         break;
       case "TechDiscoveredNotification":
         message.type = "techDiscovered";
-        this._currentSessionId = message.sessionId;
-
-        // Check if the session token already exists. If exists, continue to use the same one.
-        // If not, generate a new token.
-        if (!this.sessionTokenMap[this._currentSessionId]) {
-          this.sessionTokenMap[this._currentSessionId] = UUIDGenerator.generateUUID().toString();
-        }
         // Update the upper layers with a session token (alias)
-        message.sessionToken = this.sessionTokenMap[this._currentSessionId];
+        message.sessionToken =
+          SessionHelper.registerSession(message.sessionId, message.techList);
         // Do not expose the actual session to the content
         delete message.sessionId;
 
         gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
         break;
       case "TechLostNotification":
         message.type = "techLost";
 
         // Update the upper layers with a session token (alias)
-        message.sessionToken = this.sessionTokenMap[this._currentSessionId];
+        message.sessionToken = SessionHelper.getToken(message.sessionId);
+        if (SessionHelper.isP2PSession(message.sessionId)) {
+          gMessageManager.onPeerLost(message.sessionToken);
+        }
+
+        SessionHelper.unregisterSession(message.sessionId);
         // Do not expose the actual session to the content
         delete message.sessionId;
 
         gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
-        gMessageManager.onPeerLost(this.sessionTokenMap[this._currentSessionId]);
-
-        delete this.sessionTokenMap[this._currentSessionId];
-        this._currentSessionId = null;
-
         break;
      case "HCIEventTransactionNotification":
         this.notifyHCIEventTransaction(message);
         break;
      case "ConfigResponse":
         if (message.status === NFC.NFC_SUCCESS) {
           this.powerLevel = message.powerLevel;
         }
@@ -479,48 +530,40 @@ Nfc.prototype = {
      *     - SEName reflects the originating SE. It must be compliant with
      *       SIMAlliance Open Mobile APIs
      *     - AID reflects the originating UICC applet identifier
      * 3) Data - Data payload of the transaction notification, if any.
      */
     gSystemMessenger.broadcastMessage("nfc-hci-event-transaction", message);
   },
 
-  nfcService: null,
-
-  sessionTokenMap: null,
-
-  targetsByRequestId: null,
-
   /**
    * Process a message from the gMessageManager.
    */
   receiveMessage: function receiveMessage(message) {
     let isPowerAPI = message.name == "NFC:StartPoll" ||
                      message.name == "NFC:StopPoll"  ||
                      message.name == "NFC:PowerOff";
 
     if (!isPowerAPI) {
       if (this.powerLevel != NFC.NFC_POWER_LEVEL_ENABLED) {
         debug("NFC is not enabled. current powerLevel:" + this.powerLevel);
         this.sendNfcErrorResponse(message, NFC.NFC_GECKO_ERROR_NOT_ENABLED);
         return null;
       }
 
-      // Update the current sessionId before sending to the NFC service.
-      message.data.sessionId = this._currentSessionId;
-    }
+      // Sanity check on sessionToken.
+      if (!SessionHelper.isValidToken(message.data.sessionToken)) {
+        debug("Invalid Session Token: " + message.data.sessionToken);
+        this.sendNfcErrorResponse(message, NFC.NFC_ERROR_BAD_SESSION_ID);
+        return null;
+      }
 
-    // Sanity check on sessionId
-    let sessionToken = this.sessionTokenMap[this._currentSessionId];
-    if (message.data.sessionToken && (message.data.sessionToken !== sessionToken)) {
-      debug("Invalid Session Token: " + message.data.sessionToken +
-            " Expected Session Token: " + sessionToken);
-      this.sendNfcErrorResponse(message, NFC.NFC_ERROR_BAD_SESSION_ID);
-      return null;
+      // Update the current sessionId before sending to the NFC service.
+      message.data.sessionId = SessionHelper.getId(message.data.sessionToken);
     }
 
     switch (message.name) {
       case "NFC:StartPoll":
         this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_ENABLED,
                         requestId: message.data.requestId});
         break;
       case "NFC:StopPoll":
--- a/dom/settings/SettingsRequestManager.jsm
+++ b/dom/settings/SettingsRequestManager.jsm
@@ -661,22 +661,23 @@ let SettingsRequestManager = {
         if (DEBUG) debug("Wrong observer topic: " + aTopic);
         break;
     }
   },
 
   sendSettingsChange: function(aKey, aValue, aIsServiceLock) {
     this.broadcastMessage("Settings:Change:Return:OK",
       { key: aKey, value: aValue });
-    Services.obs.notifyObservers(this, kMozSettingsChangedObserverTopic,
-      JSON.stringify({
-        key: aKey,
-        value: aValue,
-        isInternalChange: aIsServiceLock
-      }));
+    var setting = {
+      key: aKey,
+      value: aValue,
+      isInternalChange: aIsServiceLock
+    };
+    setting.wrappedJSObject = setting;
+    Services.obs.notifyObservers(setting, kMozSettingsChangedObserverTopic, "");
   },
 
   broadcastMessage: function broadcastMessage(aMsgName, aContent) {
     if (DEBUG) debug("Broadcast");
     this.children.forEach(function(msgMgr) {
       let principal = this.mmPrincipals.get(msgMgr);
       if (!principal) {
         if (DEBUG) debug("Cannot find principal for message manager to check permissions");
--- a/dom/system/NetworkGeolocationProvider.js
+++ b/dom/system/NetworkGeolocationProvider.js
@@ -259,21 +259,23 @@ WifiGeoPositionProvider.prototype = {
   listener: null,
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != SETTINGS_CHANGED_TOPIC) {
       return;
     }
 
     try {
-      let setting = JSON.parse(aData);
-      if (setting.key == SETTINGS_DEBUG_ENABLED) {
-        gLoggingEnabled = setting.value;
-      } else if (setting.key == SETTINGS_WIFI_ENABLED) {
-        gWifiScanningEnabled = setting.value;
+      if ("wrappedJSObject" in aSubject) {
+        aSubject = aSubject.wrappedJSObject;
+      }
+      if (aSubject.key == SETTINGS_DEBUG_ENABLED) {
+        gLoggingEnabled = aSubject.value;
+      } else if (aSubject.key == SETTINGS_WIFI_ENABLED) {
+        gWifiScanningEnabled = aSubject.value;
       }
     } catch (e) {
     }
   },
 
   resetTimer: function() {
     if (this.timer) {
       this.timer.cancel();
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -36,16 +36,18 @@
 #include "BluetoothCommon.h"
 #include "BluetoothHfpManagerBase.h"
 
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsXULAppAPI.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 using namespace mozilla::dom::gonk;
 using namespace android;
 using namespace mozilla::hal;
 using namespace mozilla;
 using namespace mozilla::dom::bluetooth;
 
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args)
@@ -345,46 +347,31 @@ AudioManager::Observe(nsISupports* aSubj
   else if (!strcmp(aTopic, AUDIO_CHANNEL_PROCESS_CHANGED)) {
     HandleAudioChannelProcessChanged();
     return NS_OK;
   }
 
   // To process the volume control on each audio channel according to
   // change of settings
   else if (!strcmp(aTopic, MOZ_SETTINGS_CHANGE_ID)) {
-    AutoSafeJSContext cx;
-    nsDependentString dataStr(aData);
-    JS::Rooted<JS::Value> val(cx);
-    if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
-        !val.isObject()) {
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    JSContext* cx = jsapi.cx();
+    RootedDictionary<dom::SettingChangeNotification> setting(cx);
+    if (!WrappedJSToDictionary(cx, aSubject, setting)) {
       return NS_OK;
     }
-
-    JS::Rooted<JSObject*> obj(cx, &val.toObject());
-    JS::Rooted<JS::Value> key(cx);
-    if (!JS_GetProperty(cx, obj, "key", &key) ||
-        !key.isString()) {
+    if (!setting.mKey.EqualsASCII("audio.volume.bt_sco")) {
+      return NS_OK;
+    }
+    if (!setting.mValue.isNumber()) {
       return NS_OK;
     }
 
-    JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key));
-    if (!jsKey) {
-      return NS_OK;
-    }
-    nsAutoJSString keyStr;
-    if (!keyStr.init(cx, jsKey) || !keyStr.EqualsLiteral("audio.volume.bt_sco")) {
-      return NS_OK;
-    }
-
-    JS::Rooted<JS::Value> value(cx);
-    if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) {
-      return NS_OK;
-    }
-
-    int32_t index = value.toInt32();
+    int32_t index = setting.mValue.toNumber();
     SetStreamVolumeIndex(AUDIO_STREAM_BLUETOOTH_SCO, index);
 
     return NS_OK;
   }
 
   NS_WARNING("Unexpected topic in AudioManager");
   return NS_ERROR_FAILURE;
 }
--- a/dom/system/gonk/AutoMounterSetting.cpp
+++ b/dom/system/gonk/AutoMounterSetting.cpp
@@ -15,27 +15,31 @@
 #include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #undef LOG
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AutoMounterSetting" , ## args)
 #define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "AutoMounterSetting" , ## args)
 
 #define UMS_MODE                  "ums.mode"
 #define UMS_STATUS                "ums.status"
 #define UMS_VOLUME_ENABLED_PREFIX "ums.volume."
 #define UMS_VOLUME_ENABLED_SUFFIX ".enabled"
 #define MOZSETTINGS_CHANGED       "mozsettings-changed"
 
+using namespace mozilla::dom;
+
 namespace mozilla {
 namespace system {
 
 class SettingsServiceCallback MOZ_FINAL : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
@@ -230,62 +234,45 @@ AutoMounterSetting::Observe(nsISupports*
   }
 
   // Note that this function gets called for any and all settings changes,
   // so we need to carefully check if we have the one we're interested in.
   //
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"ums.autoMount","value":true}
 
-  mozilla::AutoSafeJSContext cx;
-  nsDependentString dataStr(aData);
-  JS::Rooted<JS::Value> val(cx);
-  if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
-      !val.isObject()) {
-    return NS_OK;
-  }
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) ||
-      !key.isString()) {
-    return NS_OK;
-  }
-
-  JSString *jsKey = JS::ToString(cx, key);
-  nsAutoJSString keyStr;
-  if (!keyStr.init(cx, jsKey)) {
-    return NS_OK;
-  }
-
-  JS::Rooted<JS::Value> value(cx);
-  if (!JS_GetProperty(cx, obj, "value", &value)) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JSContext* cx = jsapi.cx();
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
     return NS_OK;
   }
 
   // Check for ums.mode changes
-  if (keyStr.EqualsLiteral(UMS_MODE)) {
-    if (!value.isInt32()) {
+  if (setting.mKey.EqualsASCII(UMS_MODE)) {
+    if (!setting.mValue.isInt32()) {
       return NS_OK;
     }
-    int32_t mode = value.toInt32();
+    int32_t mode = setting.mValue.toInt32();
     SetAutoMounterMode(mode);
     return NS_OK;
   }
 
   // Check for ums.volume.NAME.enabled
-  if (StringBeginsWith(keyStr, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_PREFIX)) &&
-      StringEndsWith(keyStr, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_SUFFIX))) {
-    if (!value.isBoolean()) {
+  if (StringBeginsWith(setting.mKey, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_PREFIX)) &&
+      StringEndsWith(setting.mKey, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_SUFFIX))) {
+    if (!setting.mValue.isBoolean()) {
       return NS_OK;
     }
     const size_t prefixLen = sizeof(UMS_VOLUME_ENABLED_PREFIX) - 1;
     const size_t suffixLen = sizeof(UMS_VOLUME_ENABLED_SUFFIX) - 1;
     nsDependentSubstring volumeName =
-      Substring(keyStr, prefixLen, keyStr.Length() - prefixLen - suffixLen);
-    bool isSharingEnabled = value.toBoolean();
+      Substring(setting.mKey, prefixLen, setting.mKey.Length() - prefixLen - suffixLen);
+    bool isSharingEnabled = setting.mValue.toBoolean();
     SetAutoMounterSharingMode(NS_LossyConvertUTF16toASCII(volumeName), isSharingEnabled);
     return NS_OK;
   }
 
   return NS_OK;
 }
 
 }   // namespace system
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -215,18 +215,20 @@ NetworkManager.prototype = {
                                          Ci.nsIObserver,
                                          Ci.nsISettingsServiceCallback]),
 
   // nsIObserver
 
   observe: function(subject, topic, data) {
     switch (topic) {
       case TOPIC_MOZSETTINGS_CHANGED:
-        let setting = JSON.parse(data);
-        this.handle(setting.key, setting.value);
+        if ("wrappedJSObject" in subject) {
+          subject = subject.wrappedJSObject;
+        }
+        this.handle(subject.key, subject.value);
         break;
       case TOPIC_PREF_CHANGED:
         this._manageOfflineStatus =
           Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
         debug(PREF_MANAGE_OFFLINE_STATUS + " has changed to " + this._manageOfflineStatus);
         break;
       case TOPIC_XPCOM_SHUTDOWN:
         Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -955,18 +955,20 @@ XPCOMUtils.defineLazyGetter(this, "gData
     },
 
     /**
      * nsIObserver interface methods.
      */
     observe: function(subject, topic, data) {
       switch (topic) {
         case kMozSettingsChangedObserverTopic:
-          let setting = JSON.parse(data);
-          this.handle(setting.key, setting.value);
+          if ("wrappedJSObject" in subject) {
+            subject = subject.wrappedJSObject;
+          }
+          this.handle(subject.key, subject.value);
           break;
         case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
           this._shutdown();
           break;
       }
     },
   };
 });
@@ -3047,18 +3049,20 @@ RadioInterface.prototype = {
     gMessageManager.sendIccMessage("RIL:StkCommand", this.clientId, message);
   },
 
   // nsIObserver
 
   observe: function(subject, topic, data) {
     switch (topic) {
       case kMozSettingsChangedObserverTopic:
-        let setting = JSON.parse(data);
-        this.handleSettingsChange(setting.key, setting.value, setting.isInternalChange);
+        if ("wrappedJSObject" in subject) {
+          subject = subject.wrappedJSObject;
+        }
+        this.handleSettingsChange(subject.key, subject.value, subject.isInternalChange);
         break;
       case kSysClockChangeObserverTopic:
         let offset = parseInt(data, 10);
         if (this._lastNitzMessage) {
           this._lastNitzMessage.receiveTimeInMS += offset;
         }
         this._sntp.updateOffset(offset);
         break;
--- a/dom/system/gonk/TimeZoneSettingObserver.cpp
+++ b/dom/system/gonk/TimeZoneSettingObserver.cpp
@@ -17,25 +17,28 @@
 #include "nsISettingsService.h"
 #include "nsJSUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "TimeZoneSettingObserver.h"
 #include "xpcpublic.h"
 #include "nsContentUtils.h"
 #include "nsPrintfCString.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #undef LOG
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Time Zone Setting" , ## args)
 #define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "Time Zone Setting" , ## args)
 
 #define TIME_TIMEZONE       "time.timezone"
 #define MOZSETTINGS_CHANGED "mozsettings-changed"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 namespace {
 
 class TimeZoneSettingObserver : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
@@ -179,62 +182,44 @@ TimeZoneSettingObserver::~TimeZoneSettin
     observerService->RemoveObserver(this, MOZSETTINGS_CHANGED);
   }
 }
 
 NS_IMPL_ISUPPORTS(TimeZoneSettingObserver, nsIObserver)
 
 NS_IMETHODIMP
 TimeZoneSettingObserver::Observe(nsISupports *aSubject,
-                     const char *aTopic,
-                     const char16_t *aData)
+                                 const char *aTopic,
+                                 const char16_t *aData)
 {
   if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) {
     return NS_OK;
   }
 
   // Note that this function gets called for any and all settings changes,
   // so we need to carefully check if we have the one we're interested in.
   //
   // The string that we're interested in will be a JSON string that looks like:
   // {"key":"time.timezone","value":"America/Chicago"} or
   // {"key":"time.timezone","value":"UTC-05:00"}
 
   AutoSafeJSContext cx;
-
-  // Parse the JSON value.
-  nsDependentString dataStr(aData);
-  JS::Rooted<JS::Value> val(cx);
-  if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
-      !val.isObject()) {
+  RootedDictionary<SettingChangeNotification> setting(cx);
+  if (!WrappedJSToDictionary(cx, aSubject, setting)) {
     return NS_OK;
   }
-
-  // Get the key, which should be the JS string "time.timezone".
-  JS::Rooted<JSObject*> obj(cx, &val.toObject());
-  JS::Rooted<JS::Value> key(cx);
-  if (!JS_GetProperty(cx, obj, "key", &key) ||
-      !key.isString()) {
+  if (!setting.mKey.EqualsASCII(TIME_TIMEZONE)) {
     return NS_OK;
   }
-  bool match;
-  if (!JS_StringEqualsAscii(cx, key.toString(), TIME_TIMEZONE, &match) ||
-      !match) {
-    return NS_OK;
-  }
-
-  // Get the value, which should be a JS string like "America/Chicago".
-  JS::Rooted<JS::Value> value(cx);
-  if (!JS_GetProperty(cx, obj, "value", &value) ||
-      !value.isString()) {
+  if (!setting.mValue.isString()) {
     return NS_OK;
   }
 
   // Set the system timezone.
-  return SetTimeZone(value, cx);
+  return SetTimeZone(setting.mValue, cx);
 }
 
 } // anonymous namespace
 
 static mozilla::StaticRefPtr<TimeZoneSettingObserver> sTimeZoneSettingObserver;
 namespace mozilla {
 namespace system {
 void
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ScrollViewChangeEvent.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+enum ScrollState {"started", "stopped"};
+
+dictionary ScrollViewChangeEventInit : EventInit {
+  ScrollState state = "started";
+  float scrollX = 0;
+  float scrollY = 0;
+};
+
+[Constructor(DOMString type, optional ScrollViewChangeEventInit eventInit),
+ ChromeOnly]
+interface ScrollViewChangeEvent : Event {
+  readonly attribute ScrollState state;
+  readonly attribute float scrollX;
+  readonly attribute float scrollY;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/SettingChangeNotification.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Used internally by Gecko
+dictionary SettingChangeNotification {
+  DOMString key   = "";
+  any       value;
+  boolean   isInternalChange = false;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -332,16 +332,17 @@ WEBIDL_FILES = [
     'Screen.webidl',
     'ScriptProcessorNode.webidl',
     'ScrollAreaEvent.webidl',
     'Selection.webidl',
     'ServiceWorker.webidl',
     'ServiceWorkerContainer.webidl',
     'ServiceWorkerGlobalScope.webidl',
     'ServiceWorkerRegistration.webidl',
+    'SettingChangeNotification.webidl',
     'SettingsManager.webidl',
     'ShadowRoot.webidl',
     'SharedWorker.webidl',
     'SharedWorkerGlobalScope.webidl',
     'SimpleGestureEvent.webidl',
     'SocketCommon.webidl',
     'SourceBuffer.webidl',
     'SourceBufferList.webidl',
@@ -674,16 +675,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'PopStateEvent.webidl',
     'PopupBlockedEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
     'RTCDataChannelEvent.webidl',
     'RTCPeerConnectionIceEvent.webidl',
     'RTCPeerConnectionIdentityErrorEvent.webidl',
     'RTCPeerConnectionIdentityEvent.webidl',
+    'ScrollViewChangeEvent.webidl',
     'SelectionChangeEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
     'TrackEvent.webidl',
     'UDPMessageEvent.webidl',
     'UserProximityEvent.webidl',
     'USSDReceivedEvent.webidl',
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -3637,27 +3637,26 @@ WifiWorker.prototype = {
       }.bind(this));
     }
   },
 
   // nsIObserver implementation
   observe: function observe(subject, topic, data) {
     switch (topic) {
     case kMozSettingsChangedObserverTopic:
-      // The string we're interested in will be a JSON string that looks like:
-      // {"key":"wifi.enabled","value":"true"}.
-
-      let setting = JSON.parse(data);
       // To avoid WifiWorker setting the wifi again, don't need to deal with
       // the "mozsettings-changed" event fired from internal setting.
-      if (setting.isInternalChange) {
+      if ("wrappedJSObject" in subject) {
+        subject = subject.wrappedJSObject;
+      }
+      if (subject.isInternalChange) {
         return;
       }
 
-      this.handle(setting.key, setting.value);
+      this.handle(subject.key, subject.value);
       break;
 
     case "xpcom-shutdown":
       let wifiService = Cc["@mozilla.org/wifi/service;1"].getService(Ci.nsIWifiProxyService);
       wifiService.shutdown();
       let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].getService(Ci.nsIWifiCertService);
       wifiCertService.shutdown();
       break;
--- a/layout/base/SelectionCarets.cpp
+++ b/layout/base/SelectionCarets.cpp
@@ -19,24 +19,26 @@
 #include "nsIDOMDocument.h"
 #include "nsIDOMNodeFilter.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsRect.h"
 #include "nsView.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScrollViewChangeEvent.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/TreeWalker.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TouchEvents.h"
 #include "TouchCaret.h"
 #include "nsFrameSelection.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 // We treat mouse/touch move as "REAL" move event once its move distance
 // exceed this value, in CSS pixel.
 static const int32_t kMoveStartTolerancePx = 5;
 // Time for trigger scroll end event, in miliseconds.
 static const int32_t kScrollEndTimerDelay = 300;
 
 NS_IMPL_ISUPPORTS(SelectionCarets,
@@ -45,19 +47,20 @@ NS_IMPL_ISUPPORTS(SelectionCarets,
                   nsISupportsWeakReference)
 
 /*static*/ int32_t SelectionCarets::sSelectionCaretsInflateSize = 0;
 
 SelectionCarets::SelectionCarets(nsIPresShell *aPresShell)
   : mActiveTouchId(-1)
   , mCaretCenterToDownPointOffsetY(0)
   , mDragMode(NONE)
-  , mVisible(false)
+  , mAPZenabled(false)
+  , mEndCaretVisible(false)
   , mStartCaretVisible(false)
-  , mEndCaretVisible(false)
+  , mVisible(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   static bool addedPref = false;
   if (!addedPref) {
     Preferences::AddIntVarCache(&sSelectionCaretsInflateSize,
                                 "selectioncaret.inflatesize.threshold");
     addedPref = true;
@@ -856,21 +859,62 @@ SelectionCarets::NotifySelectionChanged(
   if (aReason & nsISelectionListener::KEYPRESS_REASON) {
     SetVisibility(false);
   } else {
     UpdateSelectionCarets();
   }
   return NS_OK;
 }
 
+static void
+DispatchScrollViewChangeEvent(nsIPresShell *aPresShell, const dom::ScrollState aState, const mozilla::CSSIntPoint aScrollPos)
+{
+  nsCOMPtr<nsIDocument> doc = aPresShell->GetDocument();
+  if (doc) {
+    bool ret;
+    ScrollViewChangeEventInit detail;
+    detail.mBubbles = true;
+    detail.mCancelable = false;
+    detail.mState = aState;
+    detail.mScrollX = aScrollPos.x;
+    detail.mScrollY = aScrollPos.y;
+    nsRefPtr<ScrollViewChangeEvent> event =
+      ScrollViewChangeEvent::Constructor(doc, NS_LITERAL_STRING("scrollviewchange"), detail);
+
+    event->SetTrusted(true);
+    event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
+    doc->DispatchEvent(event, &ret);
+  }
+}
+
+void
+SelectionCarets::AsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos)
+{
+  // Receives the notifications from AsyncPanZoom, sets mAPZenabled as true here
+  // to bypass the notifications from ScrollPositionChanged callbacks
+  mAPZenabled = true;
+  SetVisibility(false);
+  DispatchScrollViewChangeEvent(mPresShell, dom::ScrollState::Started, aScrollPos);
+}
+
+void
+SelectionCarets::AsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos)
+{
+  UpdateSelectionCarets();
+  DispatchScrollViewChangeEvent(mPresShell, dom::ScrollState::Stopped, aScrollPos);
+}
+
 void
 SelectionCarets::ScrollPositionChanged()
 {
-  SetVisibility(false);
-  LaunchScrollEndDetector();
+  if (!mAPZenabled) {
+    SetVisibility(false);
+    //TODO: handling scrolling for selection bubble when APZ is off
+    LaunchScrollEndDetector();
+  }
 }
 
 void
 SelectionCarets::LaunchLongTapDetector()
 {
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     return;
   }
--- a/layout/base/SelectionCarets.h
+++ b/layout/base/SelectionCarets.h
@@ -59,16 +59,20 @@ public:
   explicit SelectionCarets(nsIPresShell *aPresShell);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISELECTIONLISTENER
 
   // nsIScrollObserver
   virtual void ScrollPositionChanged() MOZ_OVERRIDE;
 
+  // AsyncPanZoom started/stopped callbacks from nsIScrollObserver
+  virtual void AsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos) MOZ_OVERRIDE;
+  virtual void AsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos) MOZ_OVERRIDE;
+
   void Terminate()
   {
     mPresShell = nullptr;
   }
 
   nsEventStatus HandleEvent(WidgetEvent* aEvent);
 
   /**
@@ -209,18 +213,21 @@ private:
   // drag distance
   nsPoint mDownPoint;
 
   // For filter multitouch event
   int32_t mActiveTouchId;
 
   nscoord mCaretCenterToDownPointOffsetY;
   DragMode mDragMode;
-  bool mVisible;
+
+  // True if AsyncPanZoom is enabled
+  bool mAPZenabled;
+  bool mEndCaretVisible;
   bool mStartCaretVisible;
-  bool mEndCaretVisible;
+  bool mVisible;
 
   // Preference
   static int32_t sSelectionCaretsInflateSize;
 };
 } // namespace mozilla
 
 #endif //SelectionCarets_h__
--- a/mobile/android/base/FormAssistPopup.java
+++ b/mobile/android/base/FormAssistPopup.java
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.FloatSize;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener;
+import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener.OnDismissCallback;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.PointF;
@@ -156,16 +158,45 @@ public class FormAssistPopup extends Rel
                     // since they can be different.
                     TextView textView = (TextView) view;
                     String value = (String) textView.getTag();
                     broadcastGeckoEvent("FormAssist:AutoComplete", value);
                     hide();
                 }
             });
 
+            // Create a ListView-specific touch listener. ListViews are given special treatment because
+            // by default they handle touches for their list items... i.e. they're in charge of drawing
+            // the pressed state (the list selector), handling list item clicks, etc.
+            final SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(mAutoCompleteList, new OnDismissCallback() {
+                @Override
+                public void onDismiss(ListView listView, final int position) {
+                    // Use the value stored with the autocomplete view, not the label text,
+                    // since they can be different.
+                    AutoCompleteListAdapter adapter = (AutoCompleteListAdapter) listView.getAdapter();
+                    Pair<String, String> item = adapter.getItem(position);
+
+                    // Remove the item from form history.
+                    broadcastGeckoEvent("FormAssist:Remove", item.second);
+
+                    // Update the list
+                    adapter.remove(item);
+                    adapter.notifyDataSetChanged();
+                    positionAndShowPopup();
+                }
+            });
+            mAutoCompleteList.setOnTouchListener(touchListener);
+
+            // Setting this scroll listener is required to ensure that during ListView scrolling,
+            // we don't look for swipes.
+            mAutoCompleteList.setOnScrollListener(touchListener.makeScrollListener());
+
+            // Setting this recycler listener is required to make sure animated views are reset.
+            mAutoCompleteList.setRecyclerListener(touchListener.makeRecyclerListener());
+
             addView(mAutoCompleteList);
         }
         
         AutoCompleteListAdapter adapter = new AutoCompleteListAdapter(mContext, R.layout.autocomplete_list_item);
         adapter.populateSuggestionsList(suggestions);
         mAutoCompleteList.setAdapter(adapter);
 
         if (setGeckoPositionData(rect, true)) {
@@ -379,18 +410,19 @@ public class FormAssistPopup extends Rel
                 }
             } catch (JSONException e) {
                 Log.e(LOGTAG, "JSONException", e);
             }
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null)
+            if (convertView == null) {
                 convertView = mInflater.inflate(mTextViewResourceId, null);
+            }
 
             Pair<String, String> item = getItem(position);
             TextView itemView = (TextView) convertView;
 
             // Set the text with the suggestion label
             itemView.setText(item.first);
 
             // Set a tag with the suggestion value
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1147,27 +1147,17 @@ public abstract class GeckoApp
 
                 if (args.contains("-profile")) {
                     Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
                     Matcher m = p.matcher(args);
                     if (m.find()) {
                         profilePath =  m.group(1);
                     }
                     if (profileName == null) {
-                        try {
-                            profileName = getDefaultProfileName();
-                        } catch (NoMozillaDirectoryException e) {
-                            Log.wtf(LOGTAG, "Unable to fetch default profile name!", e);
-                            // There's nothing at all we can do now. If the Mozilla directory
-                            // didn't exist, then we're screwed.
-                            // Crash here so we can fix the bug.
-                            throw new RuntimeException(e);
-                        }
-                        if (profileName == null)
-                            profileName = GeckoProfile.DEFAULT_PROFILE;
+                        profileName = GeckoProfile.DEFAULT_PROFILE;
                     }
                     GeckoProfile.sIsUsingCustomProfile = true;
                 }
 
                 if (profileName != null || profilePath != null) {
                     mProfile = GeckoProfile.get(this, profileName, profilePath);
                 }
             }
--- a/mobile/android/base/GeckoProfileDirectories.java
+++ b/mobile/android/base/GeckoProfileDirectories.java
@@ -112,17 +112,17 @@ public class GeckoProfileDirectories {
      *
      * @return a new File object for the Mozilla directory.
      * @throws NoMozillaDirectoryException
      *             if the directory did not exist and could not be created.
      */
     @RobocopTarget
     public static File getMozillaDirectory(Context context) throws NoMozillaDirectoryException {
         final File mozillaDir = new File(context.getFilesDir(), MOZILLA_DIR_NAME);
-        if (mozillaDir.exists() || mozillaDir.mkdirs()) {
+        if (mozillaDir.mkdirs() || mozillaDir.isDirectory()) {
             return mozillaDir;
         }
 
         // Although this leaks a path to the system log, the path is
         // predictable (unlike a profile directory), so this is fine.
         throw new NoMozillaDirectoryException("Unable to create mozilla directory at " + mozillaDir.getAbsolutePath());
     }
 
--- a/mobile/android/base/LightweightTheme.java
+++ b/mobile/android/base/LightweightTheme.java
@@ -5,68 +5,145 @@
 
 package org.mozilla.gecko;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.app.Application;
+import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewParent;
 
 public class LightweightTheme implements GeckoEventListener {
     private static final String LOGTAG = "GeckoLightweightTheme";
 
+    private static final String PREFS_URL = "lightweightTheme.headerURL";
+    private static final String PREFS_COLOR = "lightweightTheme.color";
+
     private final Application mApplication;
-    final Handler mHandler;
 
     private Bitmap mBitmap;
     private int mColor;
     private boolean mIsLight;
 
     public static interface OnChangeListener {
         // The View should change its background/text color. 
         public void onLightweightThemeChanged();
 
         // The View should reset to its default background/text color.
         public void onLightweightThemeReset();
     }
 
     private final List<OnChangeListener> mListeners;
 
+    class LightweightThemeRunnable implements Runnable {
+        private String mHeaderURL;
+        private String mColor;
+
+        private String mSavedURL;
+        private String mSavedColor;
+
+        LightweightThemeRunnable() {
+        }
+
+        LightweightThemeRunnable(final String headerURL, final String color) {
+            mHeaderURL = headerURL;
+            mColor = color;
+        }
+
+        private void loadFromPrefs() {
+            SharedPreferences prefs = GeckoSharedPrefs.forProfile(mApplication);
+            mSavedURL = prefs.getString(PREFS_URL, null);
+            mSavedColor = prefs.getString(PREFS_COLOR, null);
+        }
+
+        private void saveToPrefs() {
+            GeckoSharedPrefs.forProfile(mApplication)
+                            .edit()
+                            .putString(PREFS_URL, mHeaderURL)
+                            .putString(PREFS_COLOR, mColor)
+                            .apply();
+
+            // Let's keep the saved data in sync.
+            mSavedURL = mHeaderURL;
+            mSavedColor = mColor;
+        }
+
+        @Override
+        public void run() {
+            // Load the data from preferences, if it exists.
+            loadFromPrefs();
+
+            if (TextUtils.isEmpty(mHeaderURL)) {
+                // mHeaderURL is null is this is the early startup path. Use
+                // the saved values, if we have any.
+                mHeaderURL = mSavedURL;
+                mColor = mSavedColor;
+                if (TextUtils.isEmpty(mHeaderURL)) {
+                    // We don't have any saved values, so we probably don't have
+                    // any lightweight theme set yet.
+                    return;
+                }
+            } else if (TextUtils.equals(mHeaderURL, mSavedURL)) {
+                // If we are already using the given header, just return
+                // without doing any work.
+                return;
+            } else {
+                // mHeaderURL and mColor probably need to be saved if we get here.
+                saveToPrefs();
+            }
+
+            String croppedURL = mHeaderURL;
+            int mark = croppedURL.indexOf('?');
+            if (mark != -1) {
+                croppedURL = croppedURL.substring(0, mark);
+            }
+
+            // Get the image and convert it to a bitmap.
+            final Bitmap bitmap = BitmapUtils.decodeUrl(croppedURL);
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    setLightweightTheme(bitmap, mColor);
+                }
+            });
+        }
+    }
+
     public LightweightTheme(Application application) {
         mApplication = application;
-        mHandler = new Handler(Looper.getMainLooper());
         mListeners = new ArrayList<OnChangeListener>();
 
         // unregister isn't needed as the lifetime is same as the application.
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "LightweightTheme:Update",
             "LightweightTheme:Disable");
+
+        ThreadUtils.postToBackgroundThread(new LightweightThemeRunnable());
     }
 
     public void addListener(final OnChangeListener listener) {
         // Don't inform the listeners that attached late.
         // Their onLayout() will take care of them before their onDraw();
         mListeners.add(listener);
     }
 
@@ -77,49 +154,46 @@ public class LightweightTheme implements
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("LightweightTheme:Update")) {
                 JSONObject lightweightTheme = message.getJSONObject("data");
                 final String headerURL = lightweightTheme.getString("headerURL");
                 final String color = lightweightTheme.optString("accentcolor");
 
-                // Move any heavy lifting off the Gecko thread
-                ThreadUtils.postToBackgroundThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        String croppedURL = headerURL;
-                        int mark = croppedURL.indexOf('?');
-                        if (mark != -1)
-                            croppedURL = croppedURL.substring(0, mark);
+                ThreadUtils.postToBackgroundThread(new LightweightThemeRunnable(headerURL, color));
+            } else if (event.equals("LightweightTheme:Disable")) {
+                // Clear the saved data when a theme is disabled.
+                // Called on the Gecko thread, but should be very lightweight.
+                clearPrefs();
 
-                        // Get the image and convert it to a bitmap.
-                        final Bitmap bitmap = BitmapUtils.decodeUrl(croppedURL);
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                setLightweightTheme(bitmap, color);
-                            }
-                        });
-                    }
-                });
-            } else if (event.equals("LightweightTheme:Disable")) {
-                mHandler.post(new Runnable() {
+                ThreadUtils.postToUiThread(new Runnable() {
                     @Override
                     public void run() {
                         resetLightweightTheme();
                     }
                 });
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     /**
+     * Clear the data stored in preferences for fast path loading during startup
+     */
+    private void clearPrefs() {
+        GeckoSharedPrefs.forProfile(mApplication)
+                        .edit()
+                        .remove(PREFS_URL)
+                        .remove(PREFS_COLOR)
+                        .apply();
+    }
+
+    /**
      * Set a new lightweight theme with the given bitmap.
      * Note: This should be called on the UI thread to restrict accessing the
      * bitmap to a single thread.
      *
      * @param bitmap The bitmap used for the lightweight theme.
      * @param color  The background/accent color used for the lightweight theme.
      */
     private void setLightweightTheme(Bitmap bitmap, String color) {
@@ -131,39 +205,22 @@ public class LightweightTheme implements
         // Get the max display dimension so we can crop or expand the theme.
         DisplayMetrics dm = mApplication.getResources().getDisplayMetrics();
         int maxWidth = Math.max(dm.widthPixels, dm.heightPixels);
 
         // The lightweight theme image's width and height.
         final int bitmapWidth = bitmap.getWidth();
         final int bitmapHeight = bitmap.getHeight();
 
-        boolean useDominantColor = true;
-        if (!TextUtils.isEmpty(color)) {
-            try {
-                mColor = Color.parseColor(color);
-                useDominantColor = false;
-            } catch (IllegalArgumentException e) {
-                // Malformed color.
-            }
-        }
-
-        // Calculate the dominant color the hard way, if not given to us.
-        if (useDominantColor) {
-            // To find the dominant color, take <toolbar height> of pixels.
-            int cropLength = mApplication.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
-
-            // A cropped bitmap of the top/left pixels.
-            Bitmap cropped = Bitmap.createBitmap(bitmap,
-                                                 0, 0,
-                                                 cropLength > bitmapWidth ? bitmapWidth : cropLength,
-                                                 cropLength > bitmapHeight ? bitmapHeight : cropLength);
-
-            // Dominant color based on the cropped bitmap.
-            mColor = BitmapUtils.getDominantColor(cropped, false);
+        try {
+            mColor = Color.parseColor(color);
+        } catch (Exception e) {
+            // Malformed or missing color.
+            // Default to TRANSPARENT.
+            mColor = Color.TRANSPARENT;
         }
 
         // Calculate the luminance to determine if it's a light or a dark theme.
         double luminance = (0.2125 * ((mColor & 0x00FF0000) >> 16)) +
                            (0.7154 * ((mColor & 0x0000FF00) >> 8)) +
                            (0.0721 * (mColor &0x000000FF));
         mIsLight = luminance > 110;
 
@@ -191,18 +248,19 @@ public class LightweightTheme implements
                           bitmapHeight,
                           new Rect(0, 0, maxWidth, bitmapHeight),
                           rect);
 
             // Draw the bitmap.
             canvas.drawBitmap(bitmap, null, rect, paint);
         }
 
-        for (OnChangeListener listener : mListeners)
+        for (OnChangeListener listener : mListeners) {
             listener.onLightweightThemeChanged();
+        }
     }
 
     /**
      * Reset the lightweight theme.
      * Note: This should be called on the UI thread to restrict accessing the
      * bitmap to a single thread.
      */
     private void resetLightweightTheme() {
--- a/mobile/android/base/favicons/Favicons.java
+++ b/mobile/android/base/favicons/Favicons.java
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.favicons;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.NewTabletUI;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.cache.FaviconCache;
 import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.NonEvictingLruCache;
 import org.mozilla.gecko.util.ThreadUtils;
@@ -388,25 +389,33 @@ public class Favicons {
      * @param context A reference to the GeckoApp instance.
      */
     public static void initializeWithContext(Context context) throws IllegalStateException {
         // Prevent multiple-initialisation.
         if (!isInitialized.compareAndSet(false, true)) {
             return;
         }
 
+        final boolean isNewTabletEnabled = NewTabletUI.isEnabled(context);
         final Resources res = context.getResources();
 
-        // Decode the default Favicon ready for use.
-        defaultFavicon = BitmapFactory.decodeResource(res, R.drawable.favicon);
+        // Decode the default Favicon ready for use. We'd preferably override the drawable for
+        // different screen sizes, but since we need phone's default favicon on tablet (in
+        // ToolbarDisplayLayout), we can't.
+        final int defaultFaviconDrawableID =
+                isNewTabletEnabled ? R.drawable.new_tablet_default_favicon : R.drawable.favicon;
+        defaultFavicon = BitmapFactory.decodeResource(res, defaultFaviconDrawableID);
         if (defaultFavicon == null) {
             throw new IllegalStateException("Null default favicon was returned from the resources system!");
         }
 
-        defaultFaviconSize = res.getDimensionPixelSize(R.dimen.favicon_bg);
+        // TODO: Remove this branch when old tablet is removed.
+        final int defaultFaviconSizeDimenID =
+                isNewTabletEnabled ? R.dimen.tab_strip_favicon_size : R.dimen.favicon_bg;
+        defaultFaviconSize = res.getDimensionPixelSize(defaultFaviconSizeDimenID);
 
         // Screen-density-adjusted upper limit on favicon size. Favicons larger than this are
         // downscaled to this size or discarded.
         largestFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size);
         faviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, largestFaviconSize);
 
         // Initialize page mappings for each of our special pages.
         for (String url : AboutPages.getDefaultIconPages()) {
--- a/mobile/android/base/menu/GeckoMenuInflater.java
+++ b/mobile/android/base/menu/GeckoMenuInflater.java
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.menu;
 
 import java.io.IOException;
 
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.NewTabletUI;
 import org.mozilla.gecko.R;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.util.AttributeSet;
@@ -121,23 +122,30 @@ public class GeckoMenuInflater extends M
     }
 
     public void parseItem(ParsedItem item, AttributeSet attrs) {
         TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.MenuItem);
 
         item.id = a.getResourceId(R.styleable.MenuItem_android_id, NO_ID);
         item.order = a.getInt(R.styleable.MenuItem_android_orderInCategory, 0);
         item.title = a.getText(R.styleable.MenuItem_android_title);
-        item.iconRes = a.getResourceId(R.styleable.MenuItem_android_icon, 0);
         item.checkable = a.getBoolean(R.styleable.MenuItem_android_checkable, false);
         item.checked = a.getBoolean(R.styleable.MenuItem_android_checked, false);
         item.visible = a.getBoolean(R.styleable.MenuItem_android_visible, true);
         item.enabled = a.getBoolean(R.styleable.MenuItem_android_enabled, true);
         item.hasSubMenu = false;
 
+        // TODO: (bug 1058909) Remove this branch when we remove old tablet. We do this to
+        // avoid using a new menu resource for new tablet (which only has a new reload button).
+        if (item.id == R.id.reload && NewTabletUI.isEnabled(mContext)) {
+            item.iconRes = R.drawable.new_tablet_ic_menu_reload;
+        } else {
+            item.iconRes = a.getResourceId(R.styleable.MenuItem_android_icon, 0);
+        }
+
         if (Versions.feature11Plus) {
             item.showAsAction = a.getInt(R.styleable.MenuItem_android_showAsAction, 0);
         }
 
         a.recycle();
     }
 
     public void setValues(ParsedItem item, MenuItem menuItem) {
rename from mobile/android/base/resources/drawable-large-hdpi-v11/favicon_none.png
rename to mobile/android/base/resources/drawable-large-hdpi-v11/new_tablet_default_favicon.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..db4ee5b8ef24600cf389e1d62679ae554155d9e4
GIT binary patch
literal 822
zc$@(?1Ihe}P)<h;3K|Lk000e1NJLTq000{R000{Z1^@s6jnwp200090Nkl<ZNDZY{
zOK1~882)FoX;BbSkb?NQDL&dXj~+xUo<uzqY7xZNq}|Y@2qLH;sJBuOJcx)&Bek(L
ztr!H+B1I1t#Dg?3rXCauibbgSC`f&_cE)dt8?(DfJ27*}?En0B{{8-c!l=Y`FdSa*
zCihK*c{%aR{XMZ0c;+f;Tq;hcPm5t}bTRh`Beoej9s4$q*S|WON#8Sl7TUFijQO@;
zIP7&%#bW@h$=4HOZIG4nC^<EUI<_;?9}%Nf79c4I8pN+s85ac$8IfJOE*=oE<#r%J
zbbaFWE5oiYChNrvQh%s5x(^8lQMwAdAINbY<TwsJT+~+4lLgx%A*RtS#+*Px@Y4V~
zRtk`Ooq{I|8qp)LT>uc1PI-$9PKWz?W8HZ=<wpltC>%Y5@6&0|AcU9^Otkd(#*%iM
zs%!xZwnfhYNH(cu0q7g&)OaHvduTC~D41Yc$cSD+&qSQ@VhUh;0rb>qD^0`v{)l_4
zN9#im9Vilt9ujj!Y)WcD>?=ssrEjTa*AP(*NG1bg!OJohShfObIyH1jV;OmrL&57M
z@~tl_N0i|xNW7cxqoC2z1>{qOatT2n;M5P2C{GG|9(=Qp8t1ojO&bhHPhu>cwL41r
z?`V<}qpX#?&u}L_sqy+gA=@7N<e0y=e1WQLCUw6Yv>cdBGL}h?)_MbPP_G66S{R5D
z)Ol(HYqQT&Lva1ClIF^o|Hdswdy_`m0014+o`gtzpPmXOlgW~M&vG&oa4hreWwj@e
z0$3A><2Lp4J7YEO%B7oh&39*VGOcuJn(+lsW*RY+>rt>FjIm{=&fLMxyFaEs@5c#e
zOx<Gpf?zVZxwXAccWJ}u$Cb`xV)*Ucuf~yh{HN3A)L2O1G!rG_S4V%(iJds%d#9c4
zUgxSzIQ4}!i-O6pL_GFlmWz5!feB|C)&QMS9wjFUFc9w^r&+NBY#g@&@*c=aL@CHd
zYqEb*Hk<yk!Bd^onX3k*sS?C5;lIdHcqsMVAEDUfhSoo7<^TWy07*qoM6N<$f{bNx
AYXATM
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f8bcc2573963872c4b3bf10bb9e94e330e4d23be
GIT binary patch
literal 669
zc$@*90%HA%P)<h;3K|Lk000e1NJLTq000{R000{Z1^@s6jnwp20007HNkl<ZNDZZy
z&ui3B5Xavn*=)KNSFM#&bXQBMKUlhyB7z_Y9*P&)OJIvErY*Dxf(QK%JozWIP%Mgv
zHbuQE{yce-mfe$O588`ov4?K!?l_~o62mr`Y~JO)B=h}#GLyXFfl4s0d!AD?oF@wK
zrb4y)z0<n?ab;y!YGFtNIj&%4pDR#Rr68{W<_m`NieEtL0n$L%b^CKi2Y!X)5!LJM
z&Td7Lp*S<&_jeHSLl_32rlc8)VGL=vb}9t?+$+!w3!|<ZjYg|DQC@SDaSZ?p!C1<0
zG!NG0<K@!&x6RGp!Do7%2*#qX8ykD$rAupIX0FnYa+Yo#j5uIo7(3VN_5H%>N#DuA
z6#|SL&}b0_><Px^TU%fDsboHvjsX&2&oDOUZ!<hJ=pX6NPf$u@allkCHg7hY?Xl63
zbt~I94hk+r0pZTv!b3#3M=i(>1}0qtf<@hBu$xYAe2aSABdR&I9C0)0<`*d^|0(XN
z2Gv^J+qzSPcL8r1sPKm{JXKgn;i83m?Hg#DHKGoraYF?~E>|}XRGr0V%S#VZeyD%e
z)tQ+ywqbvwzwulc#-OLmOLyr$riN8#XNPTI(El5op+OK|C+hkv2oBfthHPcOmw;Gj
z1;t6$wOZ{Im=1%^hjrnM^z)uz)6?@OA!EEF&_!*e=fgn!JaHLackV={58l$BSJD7I
zM}asdQDzm-JC-vuZ%9|x06oWnI4)6Uc{BTxG?N;jX9<X7sqC_G=;<o3kK&xfnf*Oo
zDSQz$lxCzgVi*gy+pXKwOIK;T{-jgZW8E3FS={^wHfQPKb7dny00000NkvXXu0mjf
Dh(a?j
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4d7d1bf4526d47f67486572e57f31a3ed3d50b92
GIT binary patch
literal 989
zc$@*<10wv1P)<h;3K|Lk000e1NJLTq000{R000{Z1^@s6jnwp2000A|Nkl<ZNDaMK
zU1$_n6h7x>cFk&}YN)7K3k`)tiwgF|7ZDK@+ZQccYU+&HUER%_g7u+DsTG4hD5W51
zR^8pW8z{uSXu;N2&=>JV1VzEW_={41i$B#g**Si*W_It4GfB|k!0x@@Io~<o{XH{8
z@9D9U?H$HQw7yMbW*Ul?iOnsfWC{s2GSg#Q=pK=rwQV}uUC5pb$;DP8^E3GCID|m+
z=A|REW~(HUZ)D=8FrI4{lB*)L<Cfi5I9@2c9JDJd)!qq_H4Y9GGZSkTDK_@P`-z)u
zq%8z*FpG8L>cx{Ynwq;H(CWs%A!a4h8!?7$%v|S^-lDZd<Q#I$(PKSg*u>3P+aGm;
zw=e%-<d-qyha@}!z83H&dpY5#DrTnEnu_WU_^B9SnV3sxcNKG;r{Nc!V<t17L+~f;
z>oVOQ)USxe+uEkY>gq28H0Y$t`j-d3>gw9(hz$9dZJ8CU=pdG^Istz`<><$_zQZqs
z@%Kvq^Z5sgMI7&;pLinip;Gh%_BvF^$-t&*PFB%zchOh1l+r(7)7<WS=Y^Urxc5ym
zIW5Z3zoF6+%&1unHpVRn)P#s^l3m^T?65+usno{?8OI<r>^*cO4V<hmYuc}SA_JX%
zn;*BfWj>7vA4B{{eqR_im;@KMw$!Ku*GjqE#jpU!?(9*7X}7RE0X#nxSNAywWSVZk
z!MN_h0iY+JzYp``0N0Qvg-a9hG5URY)S$uw-qEuGPuKBi_MKCB)02?7dDW`)RF7?x
zfD)#^e7O`yl<BjL3RHIAz;FIcisgIf#QE*-idCt!{|aj0pPod_6Ji`a7>~!J9{Vr=
z{Fs$!&xFw`m4nOW4VCJyu~1JJ#KuqhW27^5c(L(~M^KFSSgG_Lq^e!s!Ms(&>Qs6w
zs@5h=uhPY=?}^wQ|3097hYx4}^ZVoR^ars9?6IOPNH)>+Bd+-3>BB@%PfyvkRmEUn
zE1CHfGy2+fdRmc}LVN<RvrC{pLW@1+Ce-<b;EnNc2Z1g2cg0+Gr|YY{Vg`ZSwEI@i
zP-DsvT<H?3Ept6awgdwBIgdWh*B1RB+mgT4d+8V578qeB)9V$h?H~q@SWn>Zqar=S
zvTxRB<Z=Q(YR;ZHed@EBa|^1D8w4LM7*XXQxj`XqA7)du|Ci;ZuI$kuPT<URqGef)
zwlQ|8BKZPK*NlLvcr#4GH^&21{o5?uOCpCJbN}y$a=E8^tiS&XIv5>wRI)0)00000
LNkvXXu0mjf^oQ{I
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4f06493f51ea070c605bdcd1d3562145ece661f5
GIT binary patch
literal 137
zc%17D@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%QURVWjv*SN$vyt(ew=S$Hh3t&
z&duMVa{7P$f9H>uU2H`?g`Sf91v5_{I&ngw-@C}csiW~>y`~=fMUOd#dWN<v({JkZ
l`1GtU^yG8lc(_%V!Q~ux!J1nVCP1?pJYD@<);T3K0RVKDEBXKc
index 9de6aa137e34eb795c056fadf82357f332582a49..2a2b42e51760ddc7ab811078a41a90e152a80093
GIT binary patch
literal 277
zc$@(h0qXvVP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0002oNkl<ZSP5fb
z7zINt1Q?Os8yXtQtgfzJ#l*yv0u+}+mZO0E=-<D8b8g<e*|vK1>c51{VL?^^bxaeo
z7!}xZKo2$n%?26OOvpSYWChTWK^CGKn^?0^LI`L(JevOh|8MZ)$B&oPauF*l>wjX*
zLU#~c56Cgy-Q6$X0(4@aL>Zmz#B0r{gYX6qP;xYcMni~D2oRDVO+%w0L?{FZ$&aR?
z(GVgO0)*rTeHwa1$Op8RM+qTN?|{}85;7mfL7=V*&{1_jO#>cMgd9F9KN<p~Aut*O
bBPIj@b#5z$ZvnYm00000NkvXXu0mjfKOkmN
index e5e9273cbb90b2468186cc6c8c3c00043b003dc0..b7159d42f700a807eb16ab59a96c92a57a52af75
GIT binary patch
literal 274
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBZhE>nhD30_oxYLxkb#IxvO~#~
zZ7#k~mqsva^&e!|a<578jo=MN&YAg|eUpNfR5cEXOh5L2&imif!;ktNZfEgmVDU(B
zaNxbtQgCi*XLx-6@+tGYR#x|1tb8ATG$Xt#e!b8$4P*Ulw`_hzJ<InB%oYuL{_oAJ
z#;Bm!Wv?1{spKAX@L)0JR200T(&7>@SwP6q*9lF)wmaaT;_>F}%rEmqHSY6W<7%*b
zvG-`3VQx=l#@g?nOw|o7dcNx1J)Lso^|bYliY?zSeSY&_nn`}|bgf;d?&tOcJ;~td
L>gTe~DWM4fI{<K>
rename from mobile/android/base/resources/drawable-large-mdpi-v11/favicon_none.png
rename to mobile/android/base/resources/drawable-large-mdpi-v11/new_tablet_default_favicon.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9c8462ed15f499d8bb346e044ff907a7dcde1730
GIT binary patch
literal 593
zc$@)I0<QguP)<h;3K|Lk000e1NJLTq000sI000sQ1^@s6R?d!B0006QNkl<ZI1z<X
zF=!KE6#n0zyM{#DTGS35x(E(ZO=@&-&?N}oPz2$y5OP9NPS<YTorR7~FljBG84i_J
z5wwFrT!d=9!A0Vrn;=$=G;O`h^CkE$=f#UR-1px1zW?LB_x~~RpU)_m3+d^z4^q>a
zqt4drjVL)p6^Ms?(Yz&voM#PU`}}IP{F3t^D#0WW%WH+}W%4RAhXUV!6RyK~82gMe
z8B?7~ON)fMFGhUR!u?C@%garl3k81}>AIds49okZ(Rw<pyL&$`+m`jC$Mr|PQ7)Go
ziNy~-0uUEiM&qn~^7dxymbJ@V6n2da!#E6T@+pZjz7TPx)%-eFw{6eo1_Ur$C``nK
zx*-5guntkQ1mHR#AahZOAllHz$I1|>&**l1lg<GeFG2sSV$kFG<J^^ixFSJ7<V{3%
z(a%Fu`R?s%BQpL30M4<XGa5a<1tD>pxhRCTL>I-YZfE&?{(w}IYcx}~cL3k+@8HE+
zt^B3OMMwS?G);?-PfXghn;DkV^}qYI<7I2L+n<?>!Wb7CjfOWiI&2>rJ93<sJk{Z8
zf8$b-GpUnj9y-qEz+L(su_2)}iZ@DBm}8DQ```*Ud#_UY$Xpm@n4rA9T3Ip$;v(0m
zvtx=-Ug)|x%3K)#Wz^B&ooZ>GZto@^AhXG#r0kB&ToC;o1xb#bt<C4B(x<n{Bt_#b
f-CZeFxfaIXN+YEbeCdTp00000NkvXXu0mjfM}Hag
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fd0c94ac7e07b7adad7a976854d59fc5b269434c
GIT binary patch
literal 408
zc$@*40cZY+P)<h;3K|Lk000e1NJLTq000sI000sQ1^@s6R?d!B0004BNkl<ZI1!bT
zu}T9$5QgX9<kYu_rIm=CjRaEID@r)XC0_6hXklX`Sc#2|g^i|3yuv61YfGOYPoTYp
zjS;<Y>r8MVgxua{;8=F%pKoU8c0m^No6VA^i_aqYNwM?Ty?x7)tE_<d8FEg*x_k?Y
zUaO7Pa+cl-(wbg0n<N8NvcasNxO3NM$^moIJQzffXZ7$f;Fdb98yDst-+xmoPtME<
zB!z-T<dZ@SpdkBcnDuCqU@Y#oA3*T=s3Y-B&kXgjwa5I5NpM&<nX+PZrn4eL?m4hz
z0`soY*<ykEN)x{3O`f427B!Nx*4~C9?KF4|9I<fKz<6eZWSDL{C7YxmjLracY4-n+
zO0U=ca0W5tlp$BMAmVcZ)xzjRL0xA-oIt^T>llh|Y>-?~tr_h?<B4zcxbw-G7_eFS
z9%Y--*y1~u3lV}9lwfq>k+;SV>ofV*RrxzvW&Z#d-<Z7N*zUXl0000<MNUMnLSTaS
C+raAp
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..87ed0dc0914251df165dee7787ebbcd7e51c959b
GIT binary patch
literal 676
zc$@*G0$crwP)<h;3K|Lk000e1NJLTq000sI000sQ1^@s6R?d!B0007ONkl<ZI1!ap
zO=wd=5T4nW7$Z$%(;)O9C<yl8AGP2i6fc4Y6$<4cHnvSnZ30R?co#f)6b}kEmd2Q<
zIaJZ2pcK3a5f!RM(1VEJK?H3<nwnaY_ttMi_T{HF(1m2anQ!LXH@ov5Q;QW2huhiX
z>Ee<Q1*81qaXS5bz4YNTi7VVpm}V^C^|{9pA7G|E^}Hnckt9DTt=Knmxmye1csLrr
z#)2<22o1%~4ha(1!Tak4)?g_MHt{$%3PE60@s7|)EPfg8rRE|j=^L>uGx1JvqM6rz
zg-}mYFM<{pY)R4LQmHeOn%I$<O71EyEw)LrVp7WQy13r5X^#25?feBh1{HzCH>DMM
zym|)pxn!EA*X!$igqDsr5O7?ZeH7IqP$`tDG?>e!)Il~Zh_pfEDMSu85LL&;B>FXh
zmag7UrM_xk+x|#ot5<lQL*$T-HCqVL?ply%Yq*AdhWi9b%A@UkE!v1&xVH``RJIo#
zTnDmKlUcax_unP3(@)J>Zn0hCjaq>jnb$=I@qoq~wZbNQS{LEG;-@2{ecEqf*9@i+
z@V9^RcLY!3M${bPwGl`59q4)WVSaAi!8;Wh>ps}i`*QF8-p|N!ZWs?i6dp;0TrPS6
z=utkFjhVV>*)(IV%J<95RP+a_+h8$>%Q+8T<*nM5G_&dC6ID=o#Cvxp@|Lv1DE_O0
zSU5XAXBa%YX^Z$3*e3oMLd-$rBDl(Ccn58C;k4+0yQ#^CmXslgy{kyI_`~47Wvkqm
zxt*NRX_eNrQ?>ZUB#tplb%P<v+{KtgV0C7%ZJVai&NE;YbJjmY%gS<Ap1M!~0000<
KMNUMnLSTZC9zd-C
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f23edf15bfe7df004b00870d1efd1048c2e4efb7
GIT binary patch
literal 132
zc%17D@N?(olHy`uVBq!ia0vp^tUxTp!3HEJ?-4l$q`W;{977}|lY9Kn{W#yiHsivN
z1ig<<I`#klZ9J03(k7AY<J88yOM~0k_;6i`qfice*5A%`b1UqUOPsn`cNK|=rA>SM
gqUne==V~v8=m%WKf>}d<0*z(xboFyt=akR{06Tmv$^ZZW
index 1b02a5bad770b6faf51540ba4cc0687985c7617e..00f5341ce78815aba39ed6c7139768c00be6f2f4
GIT binary patch
literal 201
zc%17D@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj6FglULp*qs6C_?W2tI4vwae=H
z|NsBbr=_HXv~&tOGk?9q{>n2qE9+LbudT4p1ewE*RgQd2FI#6e`)xkJlA!nh_xJUO
zuCI@`KXgQM=|iytJWP^yiHBPF4eXYh*mP)^a!mgh$X(FN6H{Z-@xZaM+fd47hZCC~
zAHPz-N)GX*O3ZnTD^4t(sv1)Aenp_N5eq|1gr>Rtt*~Q2w=sCS`njxgN@xNA$Q?+u
index 51248bea245fe37c749bd2657f601d304061707c..c18f179c334baac94579dc91430a38a33a82b3c7
GIT binary patch
literal 175
zc%17D@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjWu7jMAs)QR2@<S}6&*4;3?3OR
zShwz-y`Cl0Bmag^nZCZhw;f;2;LuQWk78SqDdwZBtHUspcM}UwMk?c(nprG@cM1fO
zudr(zZ0t3ba=GEe>Y8KH5zxde%^ok%=;d0(7^82;eW_vDVbMtm!aH`TIsR1$=VF-P
W&h6LemBkEn0fVQjpUXO@geCy6OEg&k
rename from mobile/android/base/resources/drawable-large-xhdpi-v11/favicon_none.png
rename to mobile/android/base/resources/drawable-large-xhdpi-v11/new_tablet_default_favicon.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a21969df84f1f6e1719cdbbe568b560fdc21221d
GIT binary patch
literal 1133
zc$@)k1d{uSP)<h;3K|Lk000e1NJLTq001Na001Qj1^@s6DqKrF000CuNkl<ZSP9LT
zUuau(6vxlGNm}PB+Y~n#BM+UlsI_U@4w*PDDwU$x;Xno^u}#@-6<-9A?L~xvh(7qB
z;@Yk0+NPsfnGS{#t%&SFhS=4lthlPBPS}GFMX<BW+WR}6Uy*D{@43DAW(5O*`#b0R
z`~E)p{m%usvmdO~u3`fX5Ko%D2Esc;Py+;?E-fw`86O|F{J;hLRJy%RyvKakKzt7X
zYY?%DzmEm0H#KbAa?fY;^YiL<t(#}VjY!a7B58hN03I#RBjAhS+PXK(<8I19H==S)
zTU%Q&yt{so8;+IdZk4g+*0cL3&p;g=9o6^m3}+ao)43Tu?VPvIdIE~a<C`(G`5do=
zXYB1(qY9LIeR#0{a|ex>at*Y<t7|J7)mcQ?E4l$KlY%(IWMqwV#uX6v%yx$PnpaRn
zvLmAFMC!oE;EAs!jhpzoK#9cRFobY|=S-b!NDG#-o*O-N@}jJ9mvID2Bo5X62NM?U
z2(E#p+DC^@UKTYT5+YD6X-0UyaQEX5(U0htg2MK(p%e3>#$&=3h&Q~wyppmEvsLz^
zpVaa{&yJ0Z{3&a^XV&c0FZY;Fv&{sc48s%n!pe=sCr9oujK(kNp+vXY$&)t&2-Wg$
zqw`l);id0JM{md)|C!=x&oD7CkdK2>$P#OwGfKe`!%!<F@d^tTq6G!(N;*C8>nc#9
z$9#>i4IlG9Qv|HA4bRni8W4}nQA~9AnrOgr&kh?=f#@&3%D}OW@b=}&3rVlC4Vj#N
zfb_5#1XdD={ufX@TDjFp9P8?BGXij)3pI|vjNlJI`q@$ABT0mcC-mG+3*IlTs(AO|
zN6|38<iIw`lh?tLQq-9~)i)t&DvcL`w2?l)_>Roi4A|~qT(CeLNDuUX;h^!IDgn|i
zv1GE|z|c3mWABn&dG6ihbMD}9>bS34q2#OMYtFKCDs^QARvssy-=vDXe^&9BpNaPz
zIw)!U#@7I8qtgQezpDc6V<_>C!(AIhGSaUk&4k~^?dpkt6!ZDX#mFNMXD|@br~gj7
z%G^`DZNUD<=$>o&sUI)dX}rcnAZ?V(<(3*AsvpBZ)jn?A_SVR8?ksvf5^Y_`O-=sb
z6`)-22&Bc+)6<3Uu9nPQcf%fTdw02kE^8?MbTl=$TxY27?KEy<>jG(`+1bhEx(8~{
z1gkbRaKjJE=Ge|q7DHW-HO?7VK-ywvX2#+d2xqt4dtWVY35~KXKGoFR9Ac<%WsRJ1
z4Wupf+YD~#TvN1V2ai*$Y>Q7cHb>|3Q<w6x#y(@a?Cqt+C=I3hUggd4gJQ(yfwzPg
zY{Kh?`~a<*4yXFw;(w}d%Po+v73UjBgAAwok10}b=zB~F7{S@AbF&|o#9S6U%^^F_
z{M6*dNK@-&-i)H04=~wBOMfr)X0useuk`)}?rcbIMz`t?00000NkvXXu0mjf#sN9;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e8b901553c45fed0c489eae3a037289dd7f78d2c
GIT binary patch
literal 806
zc$@(y1KIqEP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0008*Nkl<ZSP9LS
zy^9k;6u{@rCY~RS?fn6kxy_egBPiHd>KP8aeD2+4qo9I~f^wc5h!!>qa<PaIV{LWS
zft-kikYtlR1rZUE*s86@?D(Fti0h8oPw+LnGjHa--@JKmb{1GjDb?!d0r)B~W(g+N
z+pXr^Fea5SlS?&ym2-XzfbC_#_5r|gHIs*-(cKPeH!#p*b@`B@C>^I`ggX)jiaQN@
zZ6|<dWMdKPT3KtX$@Kr~4-7OJPqxtTSDEpkP=UA1G>uQGZ1yDxb`o>P)lQJ@b2TJe
zwX@$edau>=!2vp+M_t0Hr?y|HP(b8xIOx7fsktr(b_%_Rh6)WtQ0AcbR?TEzFvbfQ
zktFpw#cQM@0V3x^qyH|Y=AQ!ygo0g8AZmMoiVTPxnuG59l$v?Q0ciVXsLv_Wpdtk#
z=jNdQ0T0G)j0bI>Z>Y$D2x@5bMrtbm1h^<*q;^xEQ)H2f1&Evvhux2<bZ!f)uYl3o
z;{>9%8x($|87mg+vsfFmie>$5Q(sgf5}*R(8|dD(`pAzE?0UPku}1Gu*6Nq>aRVzO
zau{;pRct7zX!>W2-F~kV;~ZyM)&>5*z|cbRqIZQ0lsH%I%QlD!UO(WKuSu~D7GsNv
zm4x+ed;(^avmoGbe2FdQaN*J_!#W!a0Sld0b2IkJsx0YO5oTQ;*k3SM_K#^ytU%=@
z{Sq*^=@lkcpt7c4#1y#g2PQTkggJ*Wcl^S{0#vEh&oIvK1%Zhis9dWr<LP!^Uafy7
zup4w_NnO$!RbZkO1}3ry6}85q0<4V&CI5NTBV$J^bcD(^eE|=TM;IB&-O4)*LAn06
z*tFB0MQvpsbEbo<iUfxI1&+|^s(v7;zz*7Qy*JyR1N#J?wm4fa*>hVhe<nd3Lv5rm
z<Sj7JcVQXsxc+j+X+yI2&S8JIcLx2@TskK(nUDU~hJztHfq`f;{z5-hv$-#Lb;;o^
kZ)91l`0zn<lXOAl2R(l<`{}Cy3;+NC07*qoM6N<$f_9H}asU7T
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..355fde58d102f91a3bda5c4baa15fd2b9468f495
GIT binary patch
literal 1308
zc$@(o1>^dOP)<h;3K|Lk000e1NJLTq001KZ001Ni1^@s61Kfk*000EzNkl<ZSP9Kn
zU2Gdg5Z>84Cyr4<NDEcdk_W;IlphmU@KT{dTSU?%2r455`{Kk-ZNDJVhYD5nsX_><
z3PAyhS|J#k5Y(uDSfI9mfEI}dN}HrYv=X#}HVsw53FOC+_->esIP2ouJKF&xR<dqy
z=9}5iJF~NMjQvkWXE?q~ULqF=1bS967p#<k*E6myV<=5%s9u9Cj_=>!d#Mz^N+k9I
zFt&q!#SOpTU**1`ZX-z04h-8EgB5m`$Z#Bx9Y<uxGSlh9LqkJzcG%8vY&QUG*YyR-
z_<T{<ntON*IPP>JV7C||juE*%lh;`yl}gN-aY`imGG~07A#)_7bj4aYhu4VmZHB>8
zpE@bVP<gj6@tN*}c4RN@$kS$jwq!z)*iPE!U1VU3F*_=9u945(6y{n65YM-<ETo6!
zE-xP-pg$j6?sv;+T5~VFQ4A6KhZ)+l)07WaDOrCZn_6VBnx6NQylN@L7m*2d$G1^_
zY;|;m_^GC`SO1tve?q4ulaFm~ZmwLuqG1D<*fW&yoAUKe@3}AqyQ57K=U)?;3k{H6
zRyB<DC-;3U)C+`xNNj_|SRXKG5U4Gp+`=4S4{3AnW+a`2FSYrZwWScz?Z<w&@C6{~
zf3T>r5T;-xx|!HNB(y+0mzhd8Q%I(TdK;mB5E>fceNsPR1F?t~$Z+J_Mdgo@^!F@e
z+e#%u)w-gp1`g04e9W?<gFx=+h%TpMv`$ov0rAE6`ri7{!QP_M6WCK-caZ#TwkQh-
zWS0l&uA$tw3{1~Xr(dy%9R%yUy4<%&m4}JQqYf}zmCTTD6o!aFN_2GXJn0vyN2)wp
z>Tx#vlc_)nj~z1YLo?86-qsfNR8{j7VIFqE71GJbSbeV8n9w|{T{OKqPTlSaoMY7r
zBWM3)$W+Q#io*1#q>UHr`0sF9s&TRQ296VvMLmY0kmtZ-%>Sz=M}(`Q#iUBPUL-H4
zjLJU>$x!3bRL+G(zBwh`cOnH<&&Ngba>^)YkZOZ<P=mKF&>g$eswtm@XQn1j(jD}x
zU<ob(yd;p9O~mO~s??JXiB_P3*s6r%T_RnnGQBVsvRUX~FbmJ?ZKMcHME3=|;_F4a
zVr8A(@z$Ge=9iT2=mSnR3m4?b$muC>Qxo;A@L)cea)%l~*5UQ~#zuy}8P3-idRHP_
z6*>$9<fn%GKY6$JzLTTFXD*BEayLPHI9y!|uCrv=C<5v-YQ7K6tN7etGI8DxW5`3H
zXd{#1_52&0Zj5P7&9?L>druiy<C!C)3lyd24qhRBMGkH>0Qpa9K~>V9AYvbIRXg<?
z%#R&OCMWg2$mLOQMRcv@fKL%d-(|Ztc1F{nwLkgh=Vt$l$mohtIQj^Ea(twR#|$hm
z*GMm1SZs&0s>-%dPkqd+w>WsGFL8pBwuQEH!VD}hxA?KeBTlNCwke;8j<@WL;UpN0
z+%8qfN9i;4Un_hR+HX}AI|h=8bH+O6r)?-kN6)4kR{N5bRX|^k=p){4vtcQ7y|JfO
zWyen!FN9M^4*qOsYr8XM1HV5~=c$pNr5ip#xwOixbDC4Azo#R3U_O)GJ2=>T%?YdP
ziXvln5(@SBm@Ksuu_nr`dIW0DkS!z3b^2YQ(r|@=c3#!kq5iiMXUw*R=6?YO-I7mP
Sz0p_z0000<MNUMnLSTaFKXEDm
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..832ea534548c4c9ce26fc6d9066fb3b2b800a0f6
GIT binary patch
literal 139
zc%17D@N?(olHy`uVBq!ia0vp^oItF?!3HFw7YqCVQbC?Bjv*F;$vyt(ew=q;Fc6%;
z?#-X?s`LNfe{B^t3AWi%$962~VRPzBN=!*vp}r(zhPwgt;s5<DO$jGX86Q(yDk@N9
n%Pp)NY_I5g*jiNJsWrnQ3m)Oc>g|((rZafD`njxgN@xNAY1=Ao
index 8c58435e54e677e11dca78d63cd0024020bb1ee6..141ae80d597c75d9674d0c008bafdf252b835ca7
GIT binary patch
literal 363
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(FdBKfIEG}fzMWyn*W@7L;<#qT
z+Z>A~^$nlgLcXbl-8rzhfFoa_wd7ie$`A8}4rRh=7xT@G)KoT|sY|;TkSp@ho$0lj
z!0t!SEBAR{{vvezqvyPIy|d!iU+sIpdRy-K)JVJ9=TVGopUn;Tf0zB%8Rujo^yq<f
zl5y=zhkwhT^|CXc*;D;0{oOq-gBr8q-y!q#E5BcRs3N`IbF2GKxyPnY>aUmDE|Sik
zr1J6JV=jYvl9HcY1Ml1yW8SsVZehb7$1n+oLrXN0n7axVUd$nZS%1@k*OlKT#Z=+B
zL#V<%1?ez`VlN#<sf=<4CG7@FS+nIwgdS=Ad!_2$!uVtntB?XC6h*K$ieF#mevt9#
S1v6kEGI+ZBxvX<aXaWGtt&(m4
index a0c0d2ff429ea8d8707ca5744ed404fb6e848d8d..41fd4215e7ce59f1f7bda3c546fce91ad184968d
GIT binary patch
literal 349
zc$@)U0iyniP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0003bNkl<ZXa((<
zu?@mN3`KKXlZFN=5PPI$2Py{P$_#LWFaZNVkpb8NiH?FQsSXFFxLB5=QzR>apX%)7
z{O$KsIm_^X00@8p2!H?xfWZF}FsY-LWf@ZMjQKu<@HEC;>$-N{`v<SJ;EAcl)cl5e
zI_7zPch2p2f0G{|YIsFM_$dA!=QKb@zteu!19ZPrZ-!Yp(CfSlFn~lwGY1DWX-isg
zK%%0Vg9DnhB`r80QPIr70ZrPH795bMXy)L6CT&Rz4oFlqb8tYDwxk6IBr2M@WCz+^
zrDlV%mZY6L02RE&MK#aul9%fx570EtiHf{<A%*zWZm7AA<%yxVs;YNU6lZJgmcCsR
vmF3W#G4H$e`h}W5NbB`6wQ&~&eu2OT&tf$?gvKGl00000NkvXXu0mjfTsoC)
rename from mobile/android/base/resources/drawable-large-xxhdpi-v11/favicon_none.png
rename to mobile/android/base/resources/drawable-large-xxhdpi-v11/new_tablet_default_favicon.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d27bc18f0fe7c3148c0a3bf4d8a753a2c0b0a8d1
GIT binary patch
literal 1676
zc$@)@26Op|P)<h;3K|Lk000e1NJLTq001@s001`#1^@s6M;W<z000J4Nkl<ZXa(I`
zYiyHM7(VB;UD(2g;}XaW-Xj6$)~$>2hiD=$2o55Fncdpac5F)okv}9FjYfVbiJ6H;
zWZf8B%Qj=>qD(|w48g$<S)m;lqZxBCF^ixBH|#E5`}&@djqcKK=d|C~62CwFdd_p+
z=Y7ugJ?DIc#Q7;KEY!t=lE#CCb6Bj^1(d-a0P-ZE<T?`XyX=L%9*-lSg%5ENX0cVi
zO(}bCx&Qz;b@K*nI)2>YpPm=%f6axN?Ugl*uw9WHMR6Sv@W1GCx&DoWi6)&zQ%JB3
zE4NkED~aNED5cLZLrRUHO<|ZOEoL%R8lK3ehtTO)#5@7Nf1p3j?RE#n@N=W|b3^11
zIO`=Rr_nDdq3gv<Qe96^I`2LArm&4RTWT^fw4tbH#YGW<<Lrpqv!iM)X0ccn5n?!r
z-7il{6d_$>!-KENlo7$IBCN<}%b>*YJ<bHHL`d&q;Oszfq~KUf%dNX<>b%7)%xtcB
zm=S$DF1nA%xC8`F3=Q;eIp%f`$&?et#w4uNTD6K%iY-MlL<u<#2x+<Eg{>ZsTa&1R
zb3`qyyu88$jGROpi@71u2PcY8oDH=*5s&(`d!kyP%eGdm2A%!~oc$z4h2BL`uW2oc
zSC{kxZLw`zM~Ln)r6gIjzOj`s_Jtj;2FItOWn#ribw!N(3bB=%k<=hg3;_zH+<DN^
zDqbdbq_8lHeVdIER)>@l-52}-p)JmXb>GJBs+<KOC(EtXy9ls)8BsVh+zf_7`Ob#;
zi{e!<r!d@0y@RM4j3|W1BaWxOU@-LDp@Vhj<my>Hv{F~3xb3LHXySDlIuvycMuYjy
zuKM3(%BhWwB#hx!z7FS1n+ze&eZIg@aBXXA{hu=Bw7|{~#`jX0M#g8pLK#97)iE>(
z1#Wl!KQiUC!cGx3zL!d6hmj1|Dv1Z+o1ww}Lfj~768A}XG6|iW5K2o+)AaLF+c0PK
zWUlB7z%X{(kSJUuTz~-m6`k1UYS`a6F08n?a&b}$<8hx!Bht8<4TVBG>G%fGkn$rc
zTp3q(4asl~?Z&NC%18evQRDRm@F;Gl;3W+!FCnIRFu}0dMiRoVF)70%qR%wuh`V_n
zr|NtcJssJ`yyX}P=EU6tjR-MVaPuo2@Oe9rXB$oTVb^?GBd)lZ#g@8F0U9w_*g3gC
zb7r%(Y7b-dEom=+eC`ho@6wOx(RN}UgJE>c>2&@*!9GP8Pc&PrcQZ!zP5^{|0US*Y
zwbh7En7D9XXWTA!c{|S<jk(v+yY+n93<oQ|Q)<f2&3njqvGePDLEH>s+<BkZ)0vf<
z+k>&!M!qB{3JS}_S}+=O@>ZlTnEzF8Z*M|l$61AOU%M_|=*r5@yNto@CKMMD7pCjZ
zD<zo=7B0%vGt<$&K2O5LDLUNc_57S=%sr1M2%E7}3ZG2jN!H3GOKDEVvL#2lySr7p
z8k$jw3JF4Eh#!zLrf)-WX$rPxWZ`;p3a8$e6R}Sd62?6?+p6-gCGeVv;B}GA1E=w}
zas!IHEmJC1tgtXnWVYKg8Sv{OvP^~;kAcqxM+Vn5H#ZN+l#2x`C5#i5mDwL-NrrZ8
zxd)|)ft(Kv53FrzX}Kv?E=sVpFwRqCwJuL$N$uD*AD80=@2DU0TU#A}$(55wM<tBY
z6c<-4HKgcIp@A$JT0D2W67UDtXnjN%wJ<KGq@*NWpPYIUTka_-aX@|{V`Pofx&IHD
zaxz%qsA4vwe7xL(zzY~2o}Mi~QW}oW6;w~ol-TWZ<1~`NS<+)_u?c&3zU^2j`E3V}
zmf?3g2G2J&Ij&9EQtMYS@(?AtdiClUhEVP$OUX(Mzl6UWz%IN1DA}C3D(8z!moBKf
z6vWgO!-ew?P%z3U!lQ=9@btOfu_Er*&C}J+g_F%!r7KP&r51bT$9PG&eOkK0U;O@U
zpnpC_C!-1=sVvo6EQ}SL2OK*>1l|uzQ+x$NM^Mz5B1ke*3t{6fxDM35iPyAmi1>r&
z6itXzb&I2!FfPd1;P?PSWCuq15swQn>rDR**ey~4G8k26MI}N!0s9G0z<Tj!v;YV6
zl!`>T-{FL_;pkD9>eo-23*(~ksKIB<GM*reK7fnYVw~dpaQ6K0iVwE8x4SesiT)3Q
Wa}m_!qqFw_0000<MNUMnLSTZ_gB`;F
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bfd523ae7bfbb5a6526376ea7b830998151f3436
GIT binary patch
literal 1137
zc$@)o1djWOP)<h;3K|Lk000e1NJLTq001@s001@!1^@s6j74hQ000CyNkl<ZXa(Jx
zOK1~87{_OJleFo<3Mwl2LNAIS2p+uIqT&NY6hf_S(mdLv=s^TQ6uf#VJ`NtV#5Rr8
zh+4fUh>F&mmmYjU5vi{p6!8H*s!g`z_f<!irRikT{dUvN<(v5?JM;bKKRY`+8_tMR
zC>mW5)T>W$A+`&~4aV7x{Ex!nTrT~dvTIJ+_?~F{vVg|=IcH1F{RqKckB+grOPRB;
z%zh{Hnv;#0+iGfRT{ICC{suv@4#CaWIGHB{)2n=Gt5W8`so_YbVq;@+ZlFf{U<N6c
zr<#@|iizrcuo#HdPo*3Q*ro37w@ip9R)}I{3OEw5aR9@J3C6w^gR%OZh9d*(=}8Y5
z1-2EG`)Y+KR(2AOWbrTu-V|+J@6+{Lu*lb%{gdULSWw6mlY#eU(htW3+XU0{v-GK~
z6Uvb*CLtn;#9Ai&cOd9F(x<XhQO-3mgos31*KnWjF6<ZQsi0(ixp3rDQ3i-Mn@vAv
zhEdO$D0u^1?zD7OF$viiO|JCo{2pg~q4cS&UFBR=Oa`CJbPg5_wm~p4EPX0#S2<4<
zlMs93@#Q}5zYoeSkv^3zRXI--lfhr?>KYm~I4bv2`c$@5<vdkPLhOmf76-HdDz`%V
zR92;&r;5qo$2ZOSapj&%pUSF~s|dv;#NYD~SS{Hq=~LNa%2kA7GWaW*%sVmqy$*tX
zDt#(jOgU7HhZAkhn$Q|xKR2I4QZ+_h4~xlcJNFyLV^xl~#M%!sjh&<p){XYfa6FPo
zy|6nvZUk9w9|$)x_H(^9lb~=g@SY3mRUH+zc$BM>^*S#2mKg~qfJ}qs<cpPf+Lr}S
zYp}%Mp7tOn03pQnH$#KRe5^3CPxymXu!!t5)lbVTLOpvV8U_aX3;!O*p-{3mILlbh
z3ot!BPQs@jg@N8}Cyf74oq4?OADEp+D1X}$OC4bvKW?WFD->Q^QD7~J)FB9|W2#UU
z1qM5y12FwgI{{TOVBvUjzs9vQPC-=!SW7&Wgh$R<J8u;e-BZ><@$?>pg(4<6JMT1<
zr;5QlU|3^%27)O0@luLXQaMi)1Em^4sSBW#E(1|nJ5bn`>tNwTG6Z|n93_<NV3BzH
zE*Q$oFq%G<LbBejQt~?26bnaFJ2aj3!6fm^&Q-QtC~UXnky8aRS$!nlwhij9x&?)?
zkb^ZxQ=4I0T!VM^z+~{|x~V87+aaBbk$AEmR`Z*14pH*|KAeC;I5MzsJhcu^iXK20
zHF6YH)+s3Lr{ssDDky6`sFd*v3S%Jw!>=c>Q(tX`Emn3?DcJ`JSimrT$PQGN0t)*i
z0n28yA0c4nArTc685lNf{4x@F+>H5&5+xM&N1_-u+}oQT#=pl-2q#$IpVOp22QT=0
zrxV$7F9XGoc{N(~DJXA*7s^lY!KX9Zbq>o=q)Wd6>ZB*xmTzF!00000NkvXXu0mjf
Dalr#U
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5cf1790629be65fc9be591163b327290046d51db
GIT binary patch
literal 2003
zc$@*#2Q2uBP)<h;3K|Lk000e1NJLTq001@s001@!1^@s6j74hQ000M?Nkl<ZXa(I`
zYiv|S6rMA8x9!r0S|kbrib_Qwij;&OzEGors6k^D+U~a9g>K`MghYIzM*X3|cht7p
z-EB)jT@@oF_<lq*M0pj(8e?L_2T}}z1uNa|J$`p#n{|74=kD%pt=?pF_s)EC&iQ7}
zoSA#>2=PC>Jl=+f9se_4<f-#NK_qUYp&(XSS$UkJpm>U4E)ygs3PHt!sEAlZbQawt
z^gYb9M-X=(p#HDl3<P>?utR!$4NV}l*pOulu{m?*I3}Jt`BEX|m5_L$U@?_&SuvJ{
z=tl=^VixbB#k;*d`#ukc!%-`3<CvKK)26gSVP(9`lJYn>`M%KW9hWcimNRiB2|7Nx
zKD%e$htX{0$f2j&+uOTUGAb%6@?6EGFQFf=NyKJFtfH=NjEnN_K;s+XVg-o;?fQ=q
z>=lB89Eq-MZ-4Q7Gr1t;8dXyIIvj6qa(za`AgHLIsB}3=T8f56$-;)>nVF+Z;?b|S
z^L<;kG)Lk(3|m~qqu0ZIFN~KZ-YpZW_Il5dl(!ZQ&P)_AZ2y_J_UT-;Ce-}Rg4)_+
zOJx3AaKG89^jjiU?W<oPS+=5gN>T|Lj&j32iFgHcRl)scq|>P)Cb8H(2w%Swy`#dW
z(=+UKX^r)G{ZE6>EyHe=4GL54JUl)>R_|O$@d1SGj|J(wSeL(&jP52~vMy*02_A!J
zItBJ$FjVesI$|~6C07g5)<Ut&r1P-Y{?0-VVTZA9XQ$Yh7{$!wAhKyL*XVL~>I;db
z&rXtqh*j6s&(tJ+g2_3`%ox)$<{lRGWN(lDSUB8#z!ak>tg5O)petM=iSC8&V-#>@
zbuuxk@ilybR^`f;M*)ab8+!S0O*k0bYqd=}TvgTL@s5105w1Ev9ZpsE4`Oa_L#-qc
zR5dc{v2eok=%Kbi<NbKljWWBgDxd!nO#Tg!@61-_05P}QT_~M}-(nLxP9-cxO6Tgf
zQ1faPHq&~nne#QKS99H}4Fm^r7B06!>WHCy*HDncyVP}>ptZS@GHi^Em@#p+{Gwm5
zW)-S>81XvL7HssXaGBQEc>Nyub^tbLnYJ~<$3{%%7vEro6lT_*{nWSAOi(UmZm+)q
zq(XL)GHf|QjJU>%VXX5#Hoff*sB?Skmm$PI4^jj3eV{UjKXi1b{F|z`Sn$ramZr0y
z%eDyUuJf;uk{-yx{*+A}C9>cm^pzFAZnu+Ejj#R@O!3=OT9#u4mJ7x^joJ(J!~L)2
zs4L!_5GbC3&YR)|LvaT&QolGdf4OQ^%Xc;yAVI1H;k?!cS!qQ$u+uJ6jTjSsnO2I$
zN~}}Y;x*p|FL~KwldNG5Y+g&1j>!CT)*2ETwgsDB#iQ{X^2-SK#Y!zCY$2x9V<SK!
zZYVdJc)vm_?7wvxBc@C*!(Ejd<B>*NA|3sQ(kK!qDKR4qfDz*#ls2(AMTr?<0L(G&
zQrUzh;XEVrs=|$=dW;zDP#T2%ysM}*r&o2Q4x0f|=g9k3WMY$CVFfo*FtN3{#@nz9
zVSj?cC_TzNb5)?JQ-K>1)ke5C5;In|I1&@`@eyIQ1tX3S>yHL3V6k@<7aMsAE7*S<
z6C;KXYh4(%cPtqrDOg#%B<+_DOL8oN7A;zQ0-(z6Y_hl*F{6;Fe;LRrzx_f(kweP$
z9#6xNFWMf@k}~HgZ8JWa2aR-ivhnGj2f#bFZR?(K&aAQUw^`{L7<Gt{B_#Q(8E4J>
zptE!9UKKu@dbiIvU258g_%JdKojJpqKk7_}ocYG?-CHcBCI&{#Q2r<Ptw4TPy~Q%X
zM3dQ}ZF2kSZC?fqnZ(YIpu}*)_*F6OZ29`UdGqq)1?ldCeD~<*qbJifO0GldY_)zk
za$l}*+11s)(`BCHS$M={$vWcC==k?=qF@0|6da0|ncW8wGeGd^5GpLC<l;g>&N#0T
zq^SN>YwHU;<K-6a<nh*@i_uvGLO$Rvj0k_)|JT7QIyyR1dY37&^74A;w6p1LD7wgk
zVS^w@)qRh*hV?js^Ik+3yCNd;Yd93@#z7N#;-pDq^C)jT=FSA*W+UERkN@Kb2~iXv
z&~5JR>AyT2ZrNuBH6>;MWBEp{;4=W!$sRMwNo7XFMMoL>{xPqUV3r?^JZJQ;=-iHu
z7k@Hqlv2qXq0s)msJH?GzBOx@X&E>f5jds3Oyr@HHum@SoS%x6A!{Jqn+g7E`{w=A
zr%q`jEq@yN`V2G7aF>D9+FknL1?$?|52n+d(iM{$;Y+9gZcOk8P;N`FC&{<=$oy<;
zpy^hCm1~(*Vll<lwf?!twKrn)PPS%=O(+&WvWWWI0xxW^Dlx4qCJWiIeam-cr%!rL
z%PYWHLvaqGnL2fsEF^<xEFlLGU03cGeU+~SS~6KeGT&Ur?ZV#8<tjezW_adujNs^a
zxh;1Vdl9q(kzQW8W=->N*~rYu70ndPsjV#-FP-x>p)ErC`f>=&GB<()FG9Spv%b2k
zlipOF5izqRu`Fj|iNcJNo)adOmBSCt!-15kB*kQGfhOW$S26BK2vck?+5;Cevddk>
l)P;kbo3c4sny5SZ|KC9+P#WpZIcop_002ovPDHLkV1mfR)m;Dp
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c50c9cfeca8d4b1ef3f0001729e332db822905cb
GIT binary patch
literal 163
zc%17D@N?(olHy`uVBq!ia0vp^{6K8R!3HEH79Z9JQdyoZjv*erZ!aI@Y;fRlx!AKr
z|MZ7t59ID#+QNN%V;4u6L9xJ*o5gl1@+{u3MIT8Yd3RD~auu8M!A*<!Hhui@oyB1N
z-;nEm3T~#~JSP<a#f=u}0maX=7;MiC|38PrC+khM+EboyYBzWbOSt2-+7&=fVDNPH
Kb6Mw<&;$UvOg(P^
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3587d74e4ae25768ad2593044486e4b45dfe5838
GIT binary patch
literal 496
zc%17D@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2VBGHM;uuoF`1ZEFUvq-Yv5!n@
zJ;zpmQIkE*+w(4m=fI(LA35$`;jU=iRmJ`I1jCQ&$?NRr9orM;y<b-4CG-3F{&EGH
zt3~Bm1RNL`S<ZzjSf)J;4b5GaxodOrb<at6WQxzOJh!p5_Et=_wcVE7-%{20A{T7Z
zS^wFlev{|+jq3lFek`lKf4?mDyJ+^G)jJ!{@u=sdW@qyqSNhZWu~1qhPkZ(Q%VR3b
z<&tHN`>wgI`&F#I;#tMV{r~RGoBVyF*bAncEUzOb|2b%J@!C7P<~{$z4jYJM&Z;|d
zXG^c;Q5pTyn=&5nbhy{V?04DZecZk+@1N&wT>AE_`)@{%4U8-k1OyCunLIc;X0S1;
zu(<d%GYBy$EpuexWE8xt(9qJ*ktyKd<Z#4<LqSm?Ns2V(+a25EH*}w#F2CDh+t<IJ
zwy!S-x^;bw{MV<a?GLlAdj0F@w*Ap|2mkQJE+~ksFg(I1-fO}wa&Oi~HPMH6*{d`5
qe|#`6Us{uiL!p5IgNWI6z&tO)KeOV(4PX>9FnGH9xvX<aXaWH8EY0%(
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3ee80f04fdcf4af3c3f7d9d62be95531dd977af6
GIT binary patch
literal 460
zc%17D@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V4Upf;uuoF`1ba}+(Qlmt%+js
zmpykmXf_KArQQ(t&QlUvKBe(RlT=2->a8;ad-ydDGjCz?{#UH{<L1qe)64I~>ilqI
zU}O<+z$D&@dVIgP;$8MEwbj*XljawnbDy1hecRjZwfE*!_c=WNe{um|?fvz;*ID<9
zudj=YtDS%K`jktnR;`k+ez`$oGutOKi=f!+w^vARW|vAZV{m0EyBqaI@~FuR|7Xk#
zJRD+{J2G%G3SL%dXldxk6mW2IIAX$~ps0`}#WF!az>t^8gQH^x8>0$~i%&Cy5R=j}
za+FWcxe&R=^r^+1y%HBT?5#h=@i1q5nm${~>i35@m&vzmZjb&jzhZfc^|kN&ivM<|
z-~ZMz|M~`J-FUMD``e?u%rt%<6o~(joB#X4A-mP*Wgh;RuYGgD>56+Q_P;ksmThRC
ty+xmY&yUXw9|!2q>wO9dISq{u+}wLT-*ewBUI&aN22WQ%mvv4FO#oOjv)}*#
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable-large/new_tablet_menu_level.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+    <item android:maxLevel="1">
+
+        <selector>
+
+            <item gecko:state_private="true" android:drawable="@drawable/menu_pb"/>
+            <item android:drawable="@drawable/new_tablet_menu"/>
+
+        </selector>
+
+    </item>
+
+    <item android:maxLevel="2" android:drawable="@android:color/transparent"/>
+
+</level-list>
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -68,17 +68,17 @@
                   android:orientation="horizontal"
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items"/>
 
     <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button.Container"
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items"
-                  android:paddingLeft="8dip"
+                  android:paddingLeft="12dip"
                   android:paddingRight="8dip"/>
 
     <LinearLayout android:id="@id/menu_items"
                   android:layout_width="wrap_content"
                   android:layout_height="match_parent"
                   android:layout_marginLeft="3dp"
                   android:orientation="horizontal"
                   android:layout_toLeftOf="@id/menu"
--- a/mobile/android/base/resources/layout-large-v11/new_tablet_browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/new_tablet_browser_toolbar.xml
@@ -1,110 +1,103 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
-    <!-- In editing mode, the toolbar shrinks by changing to
-         dynamically constructed LayoutParams. -->
     <ImageView android:id="@+id/url_bar_entry"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
+               android:layout_alignLeft="@+id/back"
                android:layout_toLeftOf="@id/menu_items"
-               android:layout_marginLeft="20dp"
-               android:layout_marginTop="7dp"
-               android:layout_marginBottom="7dp"
+               android:layout_marginLeft="@dimen/back_button_width_half"
+               android:layout_marginTop="10dp"
+               android:layout_marginBottom="10dp"
                android:duplicateParentState="true"
                android:clickable="false"
                android:focusable="false"
                android:background="@drawable/url_bar_entry"/>
 
-    <org.mozilla.gecko.toolbar.ForwardButton style="@style/UrlBar.ImageButton.Forward"
+    <org.mozilla.gecko.toolbar.ForwardButton style="@style/UrlBar.ImageButton.Forward.NewTablet"
                                              android:id="@+id/forward"
-                                             android:layout_alignLeft="@id/url_bar_entry"/>
+                                             android:layout_alignLeft="@id/back"/>
 
-    <org.mozilla.gecko.toolbar.BackButton android:id="@+id/back"
+    <org.mozilla.gecko.toolbar.BackButton android:id="@id/back"
                                           style="@style/UrlBar.ImageButton"
-                                          android:layout_width="50dip"
-                                          android:layout_height="50dip"
-                                          android:layout_marginLeft="-15dp"
-                                          android:layout_alignLeft="@id/url_bar_entry"
+                                          android:layout_width="@dimen/back_button_width"
+                                          android:layout_height="@dimen/back_button_width"
                                           android:layout_centerVertical="true"
-                                          android:padding="13dp"
-                                          android:src="@drawable/ic_menu_back"
+                                          android:layout_marginLeft="12dp"
+                                          android:layout_alignParentLeft="true"
+                                          android:src="@drawable/new_tablet_ic_menu_back"
                                           android:contentDescription="@string/back"
                                           android:background="@drawable/url_bar_nav_button"/>
 
     <org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
                   style="@style/UrlBar.Button"
                   android:paddingLeft="12dp"
                   android:paddingRight="12dp"
                   android:visibility="gone"
                   android:orientation="horizontal"
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items"/>
 
-    <!-- We add extra padding to the left side to compensate for site security's
-         negative margin, which is used to move the site security icon closer to
-         the favicon in toolbar_display_layout. -->
+    <!-- Values of marginLeft are used to animate the forward button so don't change its value. -->
     <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button.Container"
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items"
-                  android:paddingLeft="8dip"
+                  android:paddingLeft="6dip"
                   android:paddingRight="4dip"/>
 
+    <!-- TODO: The reload asset is too small (bug 1072466) so we have white-space above and below the menu item.
+         We add marginTop to center and compensate: remove this when the final asset is added. -->
     <LinearLayout android:id="@+id/menu_items"
                   android:layout_width="wrap_content"
                   android:layout_height="match_parent"
-                  android:layout_marginLeft="3dp"
+                  android:layout_marginTop="2dp"
+                  android:layout_marginLeft="6dp"
                   android:orientation="horizontal"
-                  android:layout_toLeftOf="@id/tabs"
-                  android:layout_alignWithParentIfMissing="true"/>
+                  android:layout_toLeftOf="@id/tabs"/>
 
-    <org.mozilla.gecko.widget.ThemedImageButton android:id="@+id/tabs"
-                                                style="@style/UrlBar.ImageButton"
-                                                android:layout_width="56dip"
-                                                android:layout_toLeftOf="@id/menu"
-                                                android:background="@drawable/action_bar_button"/>
+    <org.mozilla.gecko.widget.ThemedImageButton
+            android:id="@+id/tabs"
+            style="@style/UrlBar.ImageButton"
+            android:layout_toLeftOf="@id/menu"
+            android:background="@drawable/action_bar_button"/>
 
-    <!-- The TextSwitcher should be shifted 28dp on the right, to avoid
-         the curve. On a 56dp space, centering 24dp image will leave
-         16dp on all sides. However this image has a perception of
-         2 layers. Hence to center this, an additional 4dp is added to the right.
-         The margins will be 12dp on left, 48dp on right, instead of ideal 16dp
-         and 44dp. -->
+    <!-- In a 56dp space, centering 24dp image will leave 16dp on the horizontal sides. -->
     <org.mozilla.gecko.toolbar.TabCounter android:id="@+id/tabs_counter"
                         style="@style/UrlBar.ImageButton.TabCount.NewTablet"
-                        android:layout_width="24dip"
-                        android:layout_height="24dip"
-                        android:layout_margin="16dp"
-                        android:layout_centerVertical="true"
                         android:layout_alignLeft="@id/tabs"
                         android:layout_alignRight="@id/tabs"
+                        android:layout_alignTop="@id/tabs"
+                        android:layout_alignBottom="@id/tabs"
+                        android:layout_margin="16dp"
                         gecko:layout="@layout/new_tablet_tabs_counter"/>
 
-    <org.mozilla.gecko.widget.ThemedImageButton android:id="@+id/menu"
-                                                style="@style/UrlBar.ImageButton"
-                                                android:layout_width="56dip"
-                                                android:layout_alignParentRight="true"
-                                                android:contentDescription="@string/menu"
-                                                android:background="@drawable/action_bar_button"
-                                                android:visibility="gone"/>
+    <org.mozilla.gecko.widget.ThemedImageButton
+            android:id="@+id/menu"
+            style="@style/UrlBar.ImageButton"
+            android:layout_alignParentRight="true"
+            android:layout_marginRight="6dp"
+            android:contentDescription="@string/menu"
+            android:background="@drawable/action_bar_button"
+            android:visibility="gone"/>
 
-    <org.mozilla.gecko.widget.ThemedImageView android:id="@+id/menu_icon"
-                                              style="@style/UrlBar.ImageButton"
-                                              android:layout_alignLeft="@id/menu"
-                                              android:layout_alignRight="@id/menu"
-                                              android:gravity="center_vertical"
-                                              android:src="@drawable/menu_level"
-                                              android:visibility="gone"/>
+    <org.mozilla.gecko.widget.ThemedImageView
+            android:id="@+id/menu_icon"
+            style="@style/UrlBar.ImageButton"
+            android:layout_alignLeft="@id/menu"
+            android:layout_alignRight="@id/menu"
+            android:src="@drawable/new_tablet_menu_level"
+            android:visibility="gone"/>
 
     <!-- We draw after the menu items so when they are hidden, the cancel button,
          which is thus drawn on top, may be pressed. -->
     <org.mozilla.gecko.widget.ThemedImageView
             android:id="@+id/edit_cancel"
             style="@style/UrlBar.ImageButton.Icon"
             android:layout_alignParentRight="true"
             android:src="@drawable/close_edit_mode_selector"
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -98,12 +98,12 @@
                   android:visibility="invisible"
                   android:paddingLeft="8dp"
                   android:paddingRight="8dp"/>
 
     <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button"
                   android:layout_alignLeft="@id/url_bar_entry"
                   android:layout_alignRight="@id/url_bar_entry"
-                  android:paddingLeft="4dip"
+                  android:paddingLeft="8dip"
                   android:paddingRight="4dip"/>
 
 </merge>
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -92,17 +92,17 @@
                 android:layout_height="@dimen/browser_toolbar_height"
                 android:clickable="true"
                 android:focusable="true">
 
                 <org.mozilla.gecko.toolbar.BrowserToolbar
                     android:id="@+id/browser_toolbar"
                     style="@style/BrowserToolbar"
                     android:layout_width="match_parent"
-                    android:layout_height="@dimen/browser_toolbar_height"
+                    android:layout_height="match_parent"
                     android:clickable="true"
                     android:focusable="true"
                     android:background="@drawable/url_bar_bg"/>
 
                 <org.mozilla.gecko.ActionModeCompatView android:id="@+id/actionbar"
                                                         android:layout_height="match_parent"
                                                         android:layout_width="match_parent"
                                                         style="@style/GeckoActionBar"/>
--- a/mobile/android/base/resources/layout/toolbar_display_layout.xml
+++ b/mobile/android/base/resources/layout/toolbar_display_layout.xml
@@ -5,17 +5,16 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
     <ImageButton android:id="@+id/favicon"
                  style="@style/UrlBar.ImageButton"
                  android:layout_width="@dimen/browser_toolbar_favicon_size"
                  android:scaleType="fitCenter"
-                 android:paddingLeft="4dip"
                  android:paddingRight="4dip"
                  android:layout_gravity="center_vertical"/>
 
     <ImageButton android:id="@+id/site_security"
                  style="@style/UrlBar.ImageButton"
                  android:layout_width="@dimen/browser_toolbar_lock_width"
                  android:scaleType="fitCenter"
                  android:layout_marginLeft="-4dip"
--- a/mobile/android/base/resources/values-large-v11/dimens.xml
+++ b/mobile/android/base/resources/values-large-v11/dimens.xml
@@ -2,14 +2,22 @@
 <!-- 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/. -->
 
 <resources>
 
     <dimen name="browser_toolbar_height">56dp</dimen>
     <dimen name="browser_toolbar_button_padding">16dp</dimen>
+
+    <!-- If you update one of these values, update the other. -->
+    <dimen name="back_button_width">42dp</dimen>
+    <dimen name="back_button_width_half">21dp</dimen>
+
+    <dimen name="forward_default_offset">-13dip</dimen>
+    <dimen name="new_tablet_forward_default_offset">-6dp</dimen>
+
     <dimen name="tabs_counter_size">26sp</dimen>
     <dimen name="panel_grid_view_column_width">200dp</dimen>
 
     <dimen name="tab_strip_favicon_size">16dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -16,22 +16,31 @@
         <item name="android:layout_centerVertical">true</item>
         <item name="android:src">@drawable/ic_menu_forward</item>
         <item name="android:background">@drawable/url_bar_nav_button</item>
         <!-- Start with the button hidden -->
         <item name="android:alpha">0</item>
         <item name="android:layout_marginLeft">@dimen/forward_default_offset</item>
     </style>
 
+    <style name="UrlBar.ImageButton.Forward.NewTablet">
+        <item name="android:layout_marginLeft">@dimen/new_tablet_forward_default_offset</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:paddingTop">0dp</item>
+        <item name="android:paddingBottom">0dp</item>
+        <item name="android:layout_marginTop">11.5dp</item>
+        <item name="android:layout_marginBottom">11.5dp</item>
+        <item name="android:src">@drawable/new_tablet_ic_menu_forward</item>
+    </style>
+
     <style name="UrlBar.ImageButton.TabCount.NewTablet">
         <item name="android:background">@drawable/new_tablet_tabs_count</item>
     </style>
 
-
-   <style name="UrlBar.Button.Container">
+    <style name="UrlBar.Button.Container">
         <item name="android:layout_marginTop">6dp</item>
         <item name="android:layout_marginBottom">6dp</item>
         <!-- Start with forward hidden -->
         <item name="android:orientation">horizontal</item>
     </style>
 
     <style name="TabsList" parent="TabsListBase">
          <item name="android:orientation">horizontal</item>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -83,17 +83,16 @@
     <dimen name="tabs_strip_button_width">100dp</dimen>
     <dimen name="tabs_strip_button_padding">18dp</dimen>
     <dimen name="tabs_tray_horizontal_height">156dp</dimen>
     <dimen name="text_selection_handle_width">47dp</dimen>
     <dimen name="text_selection_handle_height">58dp</dimen>
     <dimen name="text_selection_handle_shadow">11dp</dimen>
     <dimen name="validation_message_height">50dp</dimen>
     <dimen name="validation_message_margin_top">6dp</dimen>
-    <dimen name="forward_default_offset">-13dip</dimen>
     <dimen name="url_bar_offset_left">32dp</dimen>
     <dimen name="history_tab_indicator_height">50dp</dimen>
 
     <!-- PageActionButtons dimensions -->
     <dimen name="page_action_button_width">32dp</dimen>
 
     <!-- Banner -->
     <dimen name="home_banner_height">72dp</dimen>
--- a/mobile/android/base/tabs/TabStripItemView.java
+++ b/mobile/android/base/tabs/TabStripItemView.java
@@ -200,17 +200,17 @@ public class TabStripItemView extends Th
         updateFavicon(tab.getFavicon());
         titleView.setText(tab.getDisplayTitle());
         setPrivateMode(tab.isPrivate());
     }
 
     private void updateFavicon(final Bitmap favicon) {
         if (favicon == null) {
             lastFavicon = null;
-            faviconView.setImageResource(R.drawable.favicon_none);
+            faviconView.setImageResource(R.drawable.new_tablet_default_favicon);
             return;
         } else if (favicon == lastFavicon) {
             return;
         }
 
         // Cache the original so we can debounce without scaling.
         lastFavicon = favicon;
 
--- a/mobile/android/base/toolbar/BrowserToolbarNewTablet.java
+++ b/mobile/android/base/toolbar/BrowserToolbarNewTablet.java
@@ -7,17 +7,16 @@ package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.AttributeSet;
-import android.view.View;
 
 /**
  * A toolbar implementation for the tablet redesign (bug 1014156).
  * Expected to replace BrowserToolbarTablet once complete.
  */
 class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
 
     private static final int FORWARD_ANIMATION_DURATION = 450;
@@ -25,17 +24,17 @@ class BrowserToolbarNewTablet extends Br
     private final int urlBarViewOffset;
     private final int defaultForwardMargin;
 
     public BrowserToolbarNewTablet(final Context context, final AttributeSet attrs) {
         super(context, attrs);
 
         final Resources res = getResources();
         urlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
-        defaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
+        defaultForwardMargin = res.getDimensionPixelSize(R.dimen.new_tablet_forward_default_offset);
     }
 
     @Override
     public boolean isAnimating() {
         return false;
     }
 
     @Override
@@ -43,25 +42,18 @@ class BrowserToolbarNewTablet extends Br
         showUrlEditLayout();
     }
 
     @Override
     protected void triggerStopEditingTransition() {
         hideUrlEditLayout();
     }
 
-    // TODO: Copy-pasta from BrowserToolbarTablet - implement a correctly working version.
     @Override
     protected void animateForwardButton(final ForwardButtonAnimation animation) {
-        // If the forward button is not visible, we must be
-        // in the phone UI.
-        if (forwardButton.getVisibility() != View.VISIBLE) {
-            return;
-        }
-
         final boolean showing = (animation == ForwardButtonAnimation.SHOW);
 
         // if the forward button's margin is non-zero, this means it has already
         // been animated to be visible¸ and vice-versa.
         MarginLayoutParams fwdParams = (MarginLayoutParams) forwardButton.getLayoutParams();
         if ((fwdParams.leftMargin > defaultForwardMargin && showing) ||
             (fwdParams.leftMargin == defaultForwardMargin && !showing)) {
             return;
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -296,16 +296,20 @@ var SelectionHandler = {
    *                          element, or SELECT_AT_POINT to select a word.
    *                   x    - The x-coordinate for SELECT_AT_POINT.
    *                   y    - The y-coordinate for SELECT_AT_POINT.
    */
   startSelection: function sh_startSelection(aElement, aOptions = { mode: SelectionHandler.SELECT_ALL }) {
     // Clear out any existing active selection
     this._closeSelection();
 
+    if (this._isNonTextInputElement(aElement)) {
+      return false;
+    }
+
     this._initTargetInfo(aElement, this.TYPE_SELECTION);
 
     // Perform the appropriate selection method, if we can't determine method, or it fails, return
     if (!this._performSelection(aOptions)) {
       this._deactivate();
       return false;
     }
 
@@ -796,16 +800,20 @@ var SelectionHandler = {
     return (this._activeType == this.TYPE_SELECTION);
   },
 
   isElementEditableText: function (aElement) {
     return (((aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) ||
             (aElement instanceof HTMLTextAreaElement)) && !aElement.readOnly);
   },
 
+  _isNonTextInputElement: function(aElement) {
+    return (aElement instanceof HTMLInputElement && !aElement.mozIsTextField(false));
+  },
+
   /*
    * Helper function for moving the selection inside an editable element.
    *
    * @param aAnchorX the stationary handle's x-coordinate in client coordinates
    * @param aX the moved handle's x-coordinate in client coordinates
    * @param aCaretPos the current position of the caret
    */
   _moveSelectionInEditable: function sh_moveSelectionInEditable(aAnchorX, aX, aCaretPos) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5313,16 +5313,17 @@ var FormAssistant = {
 
   // Keep track of whether or not an invalid form has been submitted
   _invalidSubmit: false,
 
   init: function() {
     Services.obs.addObserver(this, "FormAssist:AutoComplete", false);
     Services.obs.addObserver(this, "FormAssist:Blocklisted", false);
     Services.obs.addObserver(this, "FormAssist:Hidden", false);
+    Services.obs.addObserver(this, "FormAssist:Remove", false);
     Services.obs.addObserver(this, "invalidformsubmit", false);
     Services.obs.addObserver(this, "PanZoom:StateChange", false);
 
     // We need to use a capturing listener for focus events
     BrowserApp.deck.addEventListener("focus", this, true);
     BrowserApp.deck.addEventListener("blur", this, true);
     BrowserApp.deck.addEventListener("click", this, true);
     BrowserApp.deck.addEventListener("input", this, false);
@@ -5330,16 +5331,17 @@ var FormAssistant = {
 
     LoginManagerParent.init();
   },
 
   uninit: function() {
     Services.obs.removeObserver(this, "FormAssist:AutoComplete");
     Services.obs.removeObserver(this, "FormAssist:Blocklisted");
     Services.obs.removeObserver(this, "FormAssist:Hidden");
+    Services.obs.removeObserver(this, "FormAssist:Remove");
     Services.obs.removeObserver(this, "invalidformsubmit");
     Services.obs.removeObserver(this, "PanZoom:StateChange");
 
     BrowserApp.deck.removeEventListener("focus", this);
     BrowserApp.deck.removeEventListener("blur", this);
     BrowserApp.deck.removeEventListener("click", this);
     BrowserApp.deck.removeEventListener("input", this);
     BrowserApp.deck.removeEventListener("pageshow", this);
@@ -5394,16 +5396,28 @@ var FormAssistant = {
 
       case "FormAssist:Blocklisted":
         this._isBlocklisted = (aData == "true");
         break;
 
       case "FormAssist:Hidden":
         this._currentInputElement = null;
         break;
+
+      case "FormAssist:Remove":
+        if (!this._currentInputElement) {
+          break;
+        }
+
+        FormHistory.update({
+          op: "remove",
+          fieldname: this._currentInputElement.name,
+          value: aData
+        });
+        break;
     }
   },
 
   notifyInvalidSubmit: function notifyInvalidSubmit(aFormElement, aInvalidElements) {
     if (!aInvalidElements.length)
       return;
 
     // Ignore this notificaiton if the current tab doesn't contain the invalid form
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -35,27 +35,28 @@ FocusSyncHandler.init();
 
 let WebProgressListener = {
   init: function() {
     let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebProgress);
     webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
   },
 
-  _requestSpec: function (aRequest) {
+  _requestSpec: function (aRequest, aPropertyName) {
     if (!aRequest || !(aRequest instanceof Ci.nsIChannel))
       return null;
-    return aRequest.QueryInterface(Ci.nsIChannel).URI.spec;
+    return aRequest.QueryInterface(Ci.nsIChannel)[aPropertyName].spec;
   },
 
   _setupJSON: function setupJSON(aWebProgress, aRequest) {
     return {
       isTopLevel: aWebProgress.isTopLevel,
       isLoadingDocument: aWebProgress.isLoadingDocument,
-      requestURI: this._requestSpec(aRequest),
+      requestURI: this._requestSpec(aRequest, "URI"),
+      originalRequestURI: this._requestSpec(aRequest, "originalURI"),
       loadType: aWebProgress.loadType,
       documentContentType: content.document && content.document.contentType
     };
   },
 
   _setupObjects: function setupObjects(aWebProgress) {
     return {
       contentWindow: content,
--- a/toolkit/devtools/apps/app-actor-front.js
+++ b/toolkit/devtools/apps/app-actor-front.js
@@ -13,17 +13,16 @@ const EventEmitter = require("devtools/t
 const PR_USEC_PER_MSEC = 1000;
 const PR_RDWR = 0x04;
 const PR_CREATE_FILE = 0x08;
 const PR_TRUNCATE = 0x20;
 
 const CHUNK_SIZE = 10000;
 
 const appTargets = new Map();
-const fronts = new Map();
 
 function addDirToZip(writer, dir, basePath) {
   let files = dir.directoryEntries;
 
   while (files.hasMoreElements()) {
     let file = files.getNext().QueryInterface(Ci.nsIFile);
 
     if (file.isHidden() ||
@@ -80,40 +79,40 @@ function zipDirectory(zipFile, dirToArch
         deferred.reject(name + ": " + message);
       }
     }
   }, null);
 
   return deferred.promise;
 }
 
-function uploadPackage(client, webappsActor, packageFile) {
+function uploadPackage(client, webappsActor, packageFile, progressCallback) {
   if (client.traits.bulk) {
-    return uploadPackageBulk(client, webappsActor, packageFile);
+    return uploadPackageBulk(client, webappsActor, packageFile, progressCallback);
   } else {
-    return uploadPackageJSON(client, webappsActor, packageFile);
+    return uploadPackageJSON(client, webappsActor, packageFile, progressCallback);
   }
 }
 
-function uploadPackageJSON(client, webappsActor, packageFile) {
+function uploadPackageJSON(client, webappsActor, packageFile, progressCallback) {
   let deferred = promise.defer();
 
   let request = {
     to: webappsActor,
     type: "uploadPackage"
   };
   client.request(request, (res) => {
     openFile(res.actor);
   });
 
   let fileSize;
   let bytesRead = 0;
 
   function emitProgress() {
-    emitInstallProgress({
+    progressCallback({
       bytesSent: bytesRead,
       totalBytes: fileSize
     });
   }
 
   function openFile(actor) {
     let openedFile;
     OS.File.open(packageFile.path)
@@ -159,17 +158,17 @@ function uploadPackageJSON(client, webap
     };
     client.request(request, (res) => {
       deferred.resolve(actor);
     });
   }
   return deferred.promise;
 }
 
-function uploadPackageBulk(client, webappsActor, packageFile) {
+function uploadPackageBulk(client, webappsActor, packageFile, progressCallback) {
   let deferred = promise.defer();
 
   let request = {
     to: webappsActor,
     type: "uploadPackage",
     bulk: true
   };
   client.request(request, (res) => {
@@ -186,17 +185,17 @@ function uploadPackageBulk(client, webap
       type: "stream",
       length: fileSize
     });
 
     request.on("bulk-send-ready", ({copyFrom}) => {
       NetUtil.asyncFetch(packageFile, function(inputStream) {
         let copying = copyFrom(inputStream);
         copying.on("progress", (e, progress) => {
-          emitInstallProgress(progress);
+          progressCallback(progress);
         });
         copying.then(() => {
           console.log("Bulk upload done");
           inputStream.close();
           deferred.resolve(actor);
         });
       });
     });
@@ -208,30 +207,37 @@ function uploadPackageBulk(client, webap
 function removeServerTemporaryFile(client, fileActor) {
   let request = {
     to: fileActor,
     type: "remove"
   };
   client.request(request);
 }
 
-function installPackaged(client, webappsActor, packagePath, appId) {
+/**
+ * progressCallback argument:
+ * Function called as packaged app installation proceeds.
+ * The progress object passed to this function contains:
+ *  * bytesSent:  The number of bytes sent so far
+ *  * totalBytes: The total number of bytes to send
+ */
+function installPackaged(client, webappsActor, packagePath, appId, progressCallback) {
   let deferred = promise.defer();
   let file = FileUtils.File(packagePath);
   let packagePromise;
   if (file.isDirectory()) {
     let tmpZipFile = FileUtils.getDir("TmpD", [], true);
     tmpZipFile.append("application.zip");
     tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
     packagePromise = zipDirectory(tmpZipFile, file)
   } else {
     packagePromise = promise.resolve(file);
   }
   packagePromise.then((zipFile) => {
-    uploadPackage(client, webappsActor, zipFile)
+    uploadPackage(client, webappsActor, zipFile, progressCallback)
         .then((fileActor) => {
           let request = {
             to: webappsActor,
             type: "install",
             appId: appId,
             upload: fileActor
           };
           client.request(request, (res) => {
@@ -256,26 +262,16 @@ function installPackaged(client, webapps
             () => removeServerTemporaryFile(client, fileActor),
             () => removeServerTemporaryFile(client, fileActor));
         });
   });
   return deferred.promise;
 }
 exports.installPackaged = installPackaged;
 
-/**
- * Emits numerous events as packaged app installation proceeds.
- * The progress object contains:
- *  * bytesSent:  The number of bytes sent so far
- *  * totalBytes: The total number of bytes to send
- */
-function emitInstallProgress(progress) {
-  exports.emit("install-progress", progress);
-}
-
 function installHosted(client, webappsActor, appId, metadata, manifest) {
   let deferred = promise.defer();
   let request = {
     to: webappsActor,
     type: "install",
     appId: appId,
     metadata: metadata,
     manifest: manifest
@@ -392,16 +388,18 @@ function getTarget(client, form) {
  */
 function App(client, webappsActor, manifest) {
   this.client = client;
   this.webappsActor = webappsActor;
   this.manifest = manifest;
 
   // This attribute is managed by the AppActorFront
   this.running = false;
+
+  this.iconURL = null;
 }
 
 App.prototype = {
   getForm: function () {
     if (this._form) {
       return promise.resolve(this._form);
     }
     let request = {
@@ -423,34 +421,74 @@ App.prototype = {
       then((form) => getTarget(this.client, form)).
       then((target) => {
         target.on("close", () => {
           delete this._form;
           delete this._target;
         });
         return this._target = target;
       });
+  },
+
+  launch: function () {
+    return launchApp(this.client, this.webappsActor,
+                     this.manifest.manifestURL);
+  },
+
+  reload: function () {
+    return reloadApp(this.client, this.webappsActor,
+                     this.manifest.manifestURL);
+  },
+
+  close: function () {
+    return closeApp(this.client, this.webappsActor,
+                    this.manifest.manifestURL)
+  },
+
+  getIcon: function () {
+    if (this.iconURL) {
+      return promise.resolve(this.iconURL);
+    }
+
+    let deferred = promise.defer();
+
+    let request = {
+      to: this.webappsActor,
+      type: "getIconAsDataURL",
+      manifestURL: this.manifest.manifestURL
+    };
+
+    this.client.request(request, res => {
+      if (res.error) {
+        deferred.reject(res.message || res.error);
+      } else if (res.url) {
+        this.iconURL = res.url;
+        deferred.resolve(res.url);
+      } else {
+        deferred.reject("Unable to fetch app icon");
+      }
+    });
+
+    return deferred.promise;
   }
 };
 
 
 /**
  * `AppActorFront` is a client for the webapps actor.
  */
 function AppActorFront(client, form) {
-  if (fronts.has(form.webappsActor)) {
-    return fronts.get(form.webappsActor);
-  }
-  fronts.set(form.webappsActor, this);
   this.client = client;
   this.actor = form.webappsActor;
 
   this._clientListener = this._clientListener.bind(this);
+  this._onInstallProgress = this._onInstallProgress.bind(this);
 
   this._listeners = [];
+  EventEmitter.decorate(this);
 }
 
 AppActorFront.prototype = {
   /**
    * List `App` instances for all currently running apps.
    */
   get runningApps() {
     if (!this._apps) {
@@ -475,68 +513,68 @@ AppActorFront.prototype = {
     return this._apps;
   },
 
   /**
    * Returns a `App` object instance for the given manifest URL
    * (and cache it per AppActorFront object)
    */
   _getApp: function (manifestURL) {
-    let app = this._apps.get(manifestURL);
+    let app = this._apps ? this._apps.get(manifestURL) : null;
     if (app) {
       return promise.resolve(app);
     } else {
       let request = {
         to: this.actor,
         type: "getApp",
         manifestURL: manifestURL
       };
       return this.client.request(request)
                  .then(res => {
                    let app = new App(this.client, this.actor, res.app);
-                   this._apps.set(manifestURL, app);
+                   if (this._apps) {
+                     this._apps.set(manifestURL, app);
+                   }
                    return app;
                  }, e => {
                    console.error("Unable to retrieve app", manifestURL, e);
                  });
     }
   },
 
   /**
    * Starts watching for app opening/closing installing/uninstalling.
    * Needs to be called before using `apps` or `runningApps` attributes.
    */
   watchApps: function (listener) {
-    this._listeners.push(listener);
+    // Fixes race between two references to the same front
+    // calling watchApps at the same time
+    if (this._loadingPromise) {
+      return this._loadingPromise;
+    }
 
     // Only call watchApps for the first listener being register,
     // for all next ones, just send fake appOpen events for already
     // opened apps
-    if (this._listeners.length > 1) {
+    if (this._apps) {
       this.runningApps.forEach((app, manifestURL) => {
         listener("appOpen", app);
       });
       return promise.resolve();
     }
 
-    let client = this.client;
-    let f = this._clientListener;
-    client.addListener("appOpen", f);
-    client.addListener("appClose", f);
-    client.addListener("appInstall", f);
-    client.addListener("appUninstall", f);
-
     // First retrieve all installed apps and create
     // related `App` object for each
     let request = {
       to: this.actor,
       type: "getAll"
     };
-    return this.client.request(request)
+    return this._loadingPromise = this.client.request(request)
       .then(res => {
+        delete this._loadingPromise;
         this._apps = new Map();
         for (let a of res.apps) {
           let app = new App(this.client, this.actor, a);
           this._apps.set(a.manifestURL, app);
         }
       })
       .then(() => {
         // Then retrieve all running apps in order to flag them as running
@@ -556,78 +594,228 @@ AppActorFront.prototype = {
                         // Fake appOpen event for all already opened
                         this._notifyListeners("appOpen", app);
                       });
         });
         return promise.all(promises);
       })
       .then(() => {
         // Finally ask to receive all app events
-        let request = {
-          to: this.actor,
-          type: "watchApps"
-        };
-        return this.client.request(request);
+        return this._listenAppEvents(listener);
       });
   },
 
+  fetchIcons: function () {
+    // On demand, retrieve apps icons in order to be able
+    // to synchronously retrieve it on `App` objects
+    let promises = [];
+    for (let [manifestURL, app] of this._apps) {
+      promises.push(app.getIcon());
+    }
+    return promise.all(promises)
+                  .then(null, () => {}); // Ignore any failure
+  },
+
+  _listenAppEvents: function (listener) {
+    this._listeners.push(listener);
+
+    if (this._listeners.length > 1) {
+      return promise.resolve();
+    }
+
+    let client = this.client;
+    let f = this._clientListener;
+    client.addListener("appOpen", f);
+    client.addListener("appClose", f);
+    client.addListener("appInstall", f);
+    client.addListener("appUninstall", f);
+
+    let request = {
+      to: this.actor,
+      type: "watchApps"
+    };
+    return this.client.request(request);
+  },
+
+  _unlistenAppEvents: function (listener) {
+    let idx = this._listeners.indexOf(listener);
+    if (idx != -1) {
+      this._listeners.splice(idx, 1);
+    }
+
+    // Until we released all listener, we don't ask to stop sending events
+    if (this._listeners.length != 0) {
+      return promise.resolve();
+    }
+
+    let client = this.client;
+    let f = this._clientListener;
+    client.removeListener("appOpen", f);
+    client.removeListener("appClose", f);
+    client.removeListener("appInstall", f);
+    client.removeListener("appUninstall", f);
+
+    // Remove `_apps` in order to allow calling watchApps again
+    // and repopulate the apps Map.
+    delete this._apps;
+
+    let request = {
+      to: this.actor,
+      type: "unwatchApps"
+    };
+    return this.client.request(request);
+  },
+
   _clientListener: function (type, message) {
+
     let { manifestURL } = message;
+
+    // Reset the app object to get a fresh copy when we (re)install the app.
+    if (type == "appInstall" && this._apps && this._apps.has(manifestURL)) {
+      this._apps.delete(manifestURL);
+    }
+
     this._getApp(manifestURL).then((app) => {
       switch(type) {
         case "appOpen":
           app.running = true;
           break;
         case "appClose":
           app.running = false;
+          break;
         case "appInstall":
           // The call to _getApp is going to create App object
+
+          // This app may have been running while being installed, so check the list
+          // of running apps again to get the right answer.
+          let request = {
+            to: this.actor,
+            type: "listRunningApps"
+          };
+          this.client.request(request)
+              .then(res => {
+                if (res.apps.indexOf(manifestURL) !== -1) {
+                  app.running = true;
+                  this._notifyListeners("appOpen", app);
+                }
+              });
           break;
         case "appUninstall":
           // Fake a appClose event if we didn't got one before uninstall
           if (app.running) {
             app.running = false;
             this._notifyListeners("appClose", app);
           }
           this._apps.delete(manifestURL);
           break;
         default:
           return;
       }
       this._notifyListeners(type, app);
+
     });
   },
 
   _notifyListeners: function (type, app) {
     this._listeners.forEach(f => {
       f(type, app);
     });
   },
 
   unwatchApps: function (listener) {
-    let idx = this._listeners.indexOf(listener);
-    if (idx != -1) {
-      this._listeners.splice(idx, 1);
-    }
+    return this._unlistenAppEvents(listener);
+  },
 
-    // Until we released all listener, we don't ask to stop sending events
-    if (this._listeners.length != 0) {
-      return;
-    }
+  /*
+   * Install a packaged app.
+   *
+   * Events are going to be emitted on the front
+   * as install progresses. Events will have the following fields:
+   *  * bytesSent:  The number of bytes sent so far
+   *  * totalBytes: The total number of bytes to send
+   */
+  installPackaged: function (packagePath, appId) {
+    let request = () => {
+      return installPackaged(this.client, this.actor, packagePath, appId,
+                             this._onInstallProgress)
+      .then(response => ({
+        appId: response.appId,
+        manifestURL: "app://" + response.appId + "/manifest.webapp"
+      }));
+    };
+    return this._install(request);
+  },
+
+  _onInstallProgress: function (progress) {
+    this.emit("install-progress", progress);
+  },
+
+  _install: function (request) {
+    let deferred = promise.defer();
+    let finalAppId = null, manifestURL = null;
+    let installs = {};
+
+    // We need to resolve only once the request is done *AND*
+    // once we receive the related appInstall message for
+    // the same manifestURL
+    let resolve = app => {
+      this._unlistenAppEvents(listener);
+      installs = null;
+      deferred.resolve({ app: app, appId: finalAppId });
+    };
 
-    let request = {
-      to: this.actor,
-      type: "unwatchApps"
+    // Listen for appInstall event, in order to resolve with
+    // the matching app object.
+    let listener = (type, app) => {
+      if (type == "appInstall") {
+        // Resolves immediately if the request has already resolved
+        // or just flag the installed app to eventually resolve
+        // when the request gets its response.
+        if (app.manifest.manifestURL === manifestURL) {
+          resolve(app);
+        } else {
+          installs[app.manifest.manifestURL] = app;
+        }
+      }
     };
-    this.client.request(request);
+    this._listenAppEvents(listener)
+        // Execute the request
+        .then(request)
+        .then(response => {
+          finalAppId = response.appId;
+          manifestURL = response.manifestURL;
 
-    let client = this.client;
-    let f = this._clientListener;
-    client.removeListener("appOpen", f);
-    client.removeListener("appClose", f);
-    client.removeListener("appInstall", f);
-    client.removeListener("appUninstall", f);
+          // Resolves immediately if the appInstall event
+          // was dispatched during the request.
+          if (manifestURL in installs) {
+            resolve(installs[manifestURL]);
+          }
+        }, deferred.reject);
+
+    return deferred.promise;
+
+  },
+
+  /*
+   * Install a hosted app.
+   *
+   * Events are going to be emitted on the front
+   * as install progresses. Events will have the following fields:
+   *  * bytesSent:  The number of bytes sent so far
+   *  * totalBytes: The total number of bytes to send
+   */
+  installHosted: function (appId, metadata, manifest) {
+    let manifestURL = metadata.manifestURL ||
+                      metadata.origin + "/manifest.webapp";
+    let request = () => {
+      return installHosted(this.client, this.actor, appId, metadata,
+                           manifest)
+        .then(response => ({
+          appId: response.appId,
+          manifestURL: manifestURL
+        }));
+    };
+    return this._install(request);
   }
 }
 
 exports.AppActorFront = AppActorFront;
-EventEmitter.decorate(exports);
 
--- a/toolkit/devtools/apps/tests/unit/head_apps.js
+++ b/toolkit/devtools/apps/tests/unit/head_apps.js
@@ -8,31 +8,35 @@ const Cr = Components.results;
 const CC = Components.Constructor;
 
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
+const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {require} = devtools;
+const {AppActorFront} = require("devtools/app-actor-front");
 
-let gClient, gActor;
+let gClient, gActor, gActorFront;
 
 function connect(onDone) {
   // Initialize a loopback remote protocol connection
   DebuggerServer.init(function () { return true; });
   // We need to register browser actors to have `listTabs` working
   // and also have a root actor
   DebuggerServer.addBrowserActors();
 
   // Setup client and actor used in all tests
   gClient = new DebuggerClient(DebuggerServer.connectPipe());
   gClient.connect(function onConnect() {
     gClient.listTabs(function onListTabs(aResponse) {
       gActor = aResponse.webappsActor;
+      gActorFront = new AppActorFront(gClient, aResponse);
       onDone();
     });
   });
 }
 
 function webappActorRequest(request, onResponse) {
   if (!gActor) {
     connect(webappActorRequest.bind(null, request, onResponse));
--- a/toolkit/devtools/apps/tests/unit/test_webappsActor.js
+++ b/toolkit/devtools/apps/tests/unit/test_webappsActor.js
@@ -1,15 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-const {require} = devtools;
-const AppActorFront = require("devtools/app-actor-front");
-const {installHosted, installPackaged} = AppActorFront;
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 let gAppId = "actor-test";
 const APP_ORIGIN = "app://" + gAppId;
 
 add_test(function testLaunchInexistantApp() {
   let request = {type: "launch", manifestURL: "http://foo.com"};
   webappActorRequest(request, function (aResponse) {
@@ -178,25 +174,25 @@ add_test(function testUninstall() {
 add_test(function testFileUploadInstall() {
   let packageFile = do_get_file("data/app.zip");
 
   // Disable the bulk trait temporarily to test the JSON upload path
   gClient.traits.bulk = false;
 
   let progressDeferred = promise.defer();
   // Ensure we get at least one progress event at the end
-  AppActorFront.on("install-progress", function onProgress(e, progress) {
+  gActorFront.on("install-progress", function onProgress(e, progress) {
     if (progress.bytesSent == progress.totalBytes) {
-      AppActorFront.off("install-progress", onProgress);
+      gActorFront.off("install-progress", onProgress);
       progressDeferred.resolve();
     }
   });
 
   let installed =
-    installPackaged(gClient, gActor, packageFile.path, gAppId)
+    gActorFront.installPackaged(packageFile.path, gAppId)
     .then(function ({ appId }) {
       do_check_eq(appId, gAppId);
     }, function (e) {
       do_throw("Failed install uploaded packaged app: " + e.error + ": " + e.message);
     });
 
   promise.all([progressDeferred.promise, installed])
     .then(() => {
@@ -207,25 +203,25 @@ add_test(function testFileUploadInstall(
 });
 
 add_test(function testBulkUploadInstall() {
   let packageFile = do_get_file("data/app.zip");
   do_check_true(gClient.traits.bulk);
 
   let progressDeferred = promise.defer();
   // Ensure we get at least one progress event at the end
-  AppActorFront.on("install-progress", function onProgress(e, progress) {
+  gActorFront.on("install-progress", function onProgress(e, progress) {
     if (progress.bytesSent == progress.totalBytes) {
-      AppActorFront.off("install-progress", onProgress);
+      gActorFront.off("install-progress", onProgress);
       progressDeferred.resolve();
     }
   });
 
   let installed =
-    installPackaged(gClient, gActor, packageFile.path, gAppId)
+    gActorFront.installPackaged(packageFile.path, gAppId)
     .then(function ({ appId }) {
       do_check_eq(appId, gAppId);
     }, function (e) {
       do_throw("Failed bulk install uploaded packaged app: " + e.error + ": " + e.message);
     });
 
   promise.all([progressDeferred.promise, installed])
     .then(run_next_test);
@@ -237,25 +233,24 @@ add_test(function testInstallHosted() {
     origin: "http://foo.com",
     installOrigin: "http://metadata.foo.com",
     manifestURL: "http://foo.com/metadata/manifest.webapp"
   };
   let manifest = {
     name: "My hosted app",
     csp: "script-src: http://foo.com"
   };
-  installHosted(gClient, gActor, gAppId, metadata, manifest).then(
-    function ({ appId }) {
-      do_check_eq(appId, gAppId);
-      run_next_test();
-    },
-    function (e) {
-      do_throw("Failed installing hosted app: " + e.error + ": " + e.message);
-    }
-  );
+  gActorFront.installHosted(gAppId, metadata, manifest)
+  .then(function ({ appId }) {
+    do_check_eq(appId, gAppId);
+    run_next_test();
+  },
+  function (e) {
+    do_throw("Failed installing hosted app: " + e.error + ": " + e.message);
+  });
 });
 
 add_test(function testCheckHostedApp() {
   let request = {type: "getAll"};
   webappActorRequest(request, function (aResponse) {
     do_check_true("apps" in aResponse);
     let apps = aResponse.apps;
     do_check_true(apps.length > 0);
--- a/toolkit/devtools/discovery/discovery.js
+++ b/toolkit/devtools/discovery/discovery.js
@@ -200,21 +200,23 @@ LocalDevice.prototype = {
 
   /**
    * Observe any changes that might be made via the Settings app
    */
   observe: function(subject, topic, data) {
     if (topic !== "mozsettings-changed") {
       return;
     }
-    let setting = JSON.parse(data);
-    if (setting.key !== LocalDevice.SETTING) {
+    if ("wrappedJSObject" in subject) {
+      subject = subject.wrappedJSObject;
+    }
+    if (subject.key !== LocalDevice.SETTING) {
       return;
     }
-    this._name = setting.value;
+    this._name = subject.value;
     log("Device: " + this._name);
   },
 
   get name() {
     return this._name;
   },
 
   set name(name) {
--- a/toolkit/devtools/server/actors/webaudio.js
+++ b/toolkit/devtools/server/actors/webaudio.js
@@ -429,20 +429,20 @@ let WebAudioActor = exports.WebAudioActo
    * Stops listening for document global changes and puts this actor
    * to hibernation. This method is called automatically just before the
    * actor is destroyed.
    */
   finalize: method(function() {
     if (!this._initialized) {
       return;
     }
-    this.tabActor = null;
     this._initialized = false;
     off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
     off(this.tabActor, "window-ready", this._onGlobalCreated);
+    this.tabActor = null;
     this._nativeToActorID = null;
     this._callWatcher.eraseRecording();
     this._callWatcher.finalize();
     this._callWatcher = null;
   }, {
    oneway: true
   }),
 
--- a/toolkit/modules/DirectoryLinksProvider.jsm
+++ b/toolkit/modules/DirectoryLinksProvider.jsm
@@ -364,27 +364,30 @@ let DirectoryLinksProvider = {
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
     this._readDirectoryLinksFile().then(rawLinks => {
       // Reset the cache of enhanced images for this new set of links
       this._enhancedLinks.clear();
 
       // all directory links have a frecency of DIRECTORY_FRECENCY
-      aCallback(rawLinks.map((link, position) => {
+      return rawLinks.map((link, position) => {
         // Stash the enhanced image for the site
         if (link.enhancedImageURI) {
           this._enhancedLinks.set(NewTabUtils.extractSite(link.url), link);
         }
 
         link.frecency = DIRECTORY_FRECENCY;
         link.lastVisitDate = rawLinks.length - position;
         return link;
-      }));
-    });
+      });
+    }).catch(ex => {
+      Cu.reportError(ex);
+      return [];
+    }).then(aCallback);
   },
 
   init: function DirectoryLinksProvider_init() {
     this._setDefaultEnhanced();
     this._addPrefsObserver();
     // setup directory file path and last download timestamp
     this._directoryFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE);
     this._lastDownloadMS = 0;
--- a/toolkit/modules/RemoteWebProgress.jsm
+++ b/toolkit/modules/RemoteWebProgress.jsm
@@ -12,25 +12,27 @@ const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function newURI(spec)
 {
     return Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
                                                     .newURI(spec, null, null);
 }
 
-function RemoteWebProgressRequest(spec)
+function RemoteWebProgressRequest(spec, originalSpec)
 {
-  this.uri = newURI(spec);
+  this._uri = newURI(spec);
+  this._originalURI = newURI(originalSpec);
 }
 
 RemoteWebProgressRequest.prototype = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIChannel]),
 
-  get URI() { return this.uri.clone(); }
+  get URI() { return this._uri.clone(); },
+  get originalURI() { return this._originalURI.clone(); }
 };
 
 function RemoteWebProgress(aManager, aIsTopLevel) {
   this._manager = aManager;
 
   this._isLoadingDocument = false;
   this._DOMWindow = null;
   this._isTopLevel = aIsTopLevel;
@@ -139,18 +141,21 @@ RemoteWebProgressManager.prototype = {
 
     // The top-level WebProgress is always the same, but because we don't
     // really have a concept of subframes/content we always creat a new object
     // for those.
     let webProgress = json.isTopLevel ? this._topLevelWebProgress
                                       : new RemoteWebProgress(this, false);
 
     // The WebProgressRequest object however is always dynamic.
-    let request = json.requestURI ? new RemoteWebProgressRequest(json.requestURI)
-                                  : null;
+    let request = null;
+    if (json.requestURI) {
+      request = new RemoteWebProgressRequest(json.requestURI,
+                                             json.originalRequestURI);
+    }
 
     // Update the actual WebProgress fields.
     webProgress._isLoadingDocument = json.isLoadingDocument;
     webProgress._DOMWindow = objects.DOMWindow;
     webProgress._loadType = json.loadType;
 
     if (json.isTopLevel) {
       this._browser._contentWindow = objects.contentWindow;
--- a/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
+++ b/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
@@ -421,16 +421,31 @@ add_task(function test_DirectoryLinksPro
 
 add_task(function test_DirectoryLinksProvider_getLinks_noLocaleData() {
   yield promiseSetupDirectoryLinksProvider({locale: 'zh-CN'});
   let links = yield fetchData();
   do_check_eq(links.length, 0);
   yield promiseCleanDirectoryLinksProvider();
 });
 
+add_task(function test_DirectoryLinksProvider_getLinks_badData() {
+  let data = {
+    "en-US": {
+      "en-US": [{url: "http://example.com", title: "US"}],
+    },
+  };
+  let dataURI = 'data:application/json,' + JSON.stringify(data);
+  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+  // Make sure we get nothing for incorrectly formatted data
+  let links = yield fetchData();
+  do_check_eq(links.length, 0);
+  yield promiseCleanDirectoryLinksProvider();
+});
+
 add_task(function test_DirectoryLinksProvider_needsDownload() {
   // test timestamping
   DirectoryLinksProvider._lastDownloadMS = 0;
   do_check_true(DirectoryLinksProvider._needsDownload);
   DirectoryLinksProvider._lastDownloadMS = Date.now();
   do_check_false(DirectoryLinksProvider._needsDownload);
   DirectoryLinksProvider._lastDownloadMS = Date.now() - (60*60*24 + 1)*1000;
   do_check_true(DirectoryLinksProvider._needsDownload);
--- a/widget/gonk/GeckoTouchDispatcher.cpp
+++ b/widget/gonk/GeckoTouchDispatcher.cpp
@@ -49,17 +49,16 @@ namespace mozilla {
 static const uint64_t kInputExpirationThresholdMs = 1000;
 static int32_t nanosecToMillisec(int64_t nanosec) { return nanosec / 1000000; }
 
 static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher;
 
 GeckoTouchDispatcher::GeckoTouchDispatcher()
   : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock")
   , mTouchEventsFiltered(false)
-  , mTouchDownCount(0)
   , mTouchTimeDiff(0)
   , mLastTouchTime(0)
 {
   // Since GeckoTouchDispatcher is initialized when input is initialized
   // and reads gfxPrefs, it is the first thing to touch gfxPrefs.
   // The first thing to touch gfxPrefs MUST occur on the main thread and init
   // the singleton
   MOZ_ASSERT(sTouchDispatcher == nullptr);
@@ -345,38 +344,21 @@ IsExpired(const MultiTouchInput& aTouch)
 {
   // No pending events, the filter state can be updated.
   uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000;
   return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs;
 }
 void
 GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput& aMultiTouch)
 {
-  if (!mTouchDownCount) {
+  if (aMultiTouch.mType == MultiTouchInput::MULTITOUCH_START &&
+      aMultiTouch.mTouches.Length() == 1) {
     mTouchEventsFiltered = IsExpired(aMultiTouch);
   }
 
-  switch (aMultiTouch.mType) {
-    case MultiTouchInput::MULTITOUCH_START:
-      mTouchDownCount++;
-      break;
-    case MultiTouchInput::MULTITOUCH_MOVE:
-      break;
-    case MultiTouchInput::MULTITOUCH_END:
-    case MultiTouchInput::MULTITOUCH_CANCEL:
-      mTouchDownCount--;
-      if (mTouchDownCount == 0) {
-        MutexAutoLock lock(mTouchQueueLock);
-        mTouchMoveEvents.clear();
-      }
-      break;
-    default:
-      break;
-  }
-
   if (mTouchEventsFiltered) {
     return;
   }
 
   bool captured = false;
   WidgetTouchEvent event = aMultiTouch.ToWidgetTouchEvent(nullptr);
   nsEventStatus status = nsWindow::DispatchInputEvent(event, &captured);
 
--- a/widget/gonk/GeckoTouchDispatcher.h
+++ b/widget/gonk/GeckoTouchDispatcher.h
@@ -61,17 +61,16 @@ private:
   // mTouchQueueLock are used to protect the vector below
   // as it is accessed on the vsync thread and main thread
   Mutex mTouchQueueLock;
   std::vector<MultiTouchInput> mTouchMoveEvents;
 
   bool mResamplingEnabled;
   bool mTouchEventsFiltered;
   bool mEnabledUniformityInfo;
-  int mTouchDownCount;
 
   // All times below are in nanoseconds
   int32_t mVsyncAdjust;     // Time from vsync we create sample times from
   int32_t mMaxPredict;      // How far into the future we're allowed to extrapolate
 
   // Amount of time between vsync and the last event that is required before we
   // resample
   int32_t mMinResampleTime;