Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 27 Oct 2015 14:15:23 -0700
changeset 305035 c6fa796af5487f5fafdef19f0b436102439d80d6
parent 305034 1a1d5630602c120dddc54c931fa3227a729d1153 (current diff)
parent 304960 2b333a1d94e805a59c619ee41a6dec7fdcce505d (diff)
child 305036 36fd2c273344330541b951543bd2c5951c798630
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound, a=merge
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -674,17 +674,17 @@ pref("javascript.options.mem.gc_allocati
 pref("javascript.options.mem.gc_decommit_threshold_mb", 1);
 pref("javascript.options.mem.gc_min_empty_chunk_count", 1);
 pref("javascript.options.mem.gc_max_empty_chunk_count", 2);
 
 // Show/Hide scrollbars when active/inactive
 pref("ui.showHideScrollbars", 1);
 pref("ui.useOverlayScrollbars", 1);
 pref("ui.scrollbarFadeBeginDelay", 450);
-pref("ui.scrollbarFadeDuration", 200);
+pref("ui.scrollbarFadeDuration", 0);
 
 // Scrollbar position follows the document `dir` attribute
 pref("layout.scrollbar.side", 1);
 
 // CSS Scroll Snapping
 pref("layout.css.scroll-snap.enabled", true);
 
 // Enable the ProcessPriorityManager, and give processes with no visible
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -398,27 +398,24 @@ var shell = {
                                .QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIWebNavigation);
     webNav.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].createInstance(Ci.nsISHistory);
 
 #ifdef MOZ_GRAPHENE
     webNav.QueryInterface(Ci.nsIDocShell).windowDraggingAllowed = true;
 #endif
 
-    this.allowedAudioChannels = new Map();
     let audioChannels = systemAppFrame.allowedAudioChannels;
     audioChannels && audioChannels.forEach(function(audioChannel) {
-      this.allowedAudioChannels.set(audioChannel.name, audioChannel);
-      audioChannel.addEventListener('activestatechanged', this);
       // Set all audio channels as unmuted by default
       // because some audio in System app will be played
       // before AudioChannelService[1] is Gaia is loaded.
       // [1]: https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/audio_channel_service.js
       audioChannel.setMuted(false);
-    }.bind(this));
+    });
 
     // On firefox mulet, shell.html is loaded in a tab
     // and we have to listen on the chrome event handler
     // to catch key events
     let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                    .getInterface(Ci.nsIWebNavigation)
                                    .QueryInterface(Ci.nsIDocShell)
                                    .chromeEventHandler || window;
@@ -678,28 +675,16 @@ var shell = {
         // the critical launch path of the app.
         SystemAppProxy._sendCustomEvent('mozChromeEvent', {
           type: 'system-first-paint'
         }, /* noPending */ true);
         break;
       case 'unload':
         this.stop();
         break;
-      case 'activestatechanged':
-        var channel = evt.target;
-        // TODO: We should get the `isActive` state from evt.isActive.
-        // Then we don't need to do `channel.isActive()` here.
-        channel.isActive().onsuccess = function(evt) {
-          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
-            type: 'system-audiochannel-state-changed',
-            name: channel.name,
-            isActive: evt.target.result
-          });
-        }.bind(this);
-        break;
     }
   },
 
   // Send an event to a specific window, document or element.
   sendEvent: function shell_sendEvent(target, type, details) {
     if (target === this.contentBrowser) {
       // We must ask SystemAppProxy to send the event in this case so
       // that event would be dispatched from frame.contentWindow instead of
@@ -899,21 +884,16 @@ var CustomEventManager = {
       case 'captive-portal-login-cancel':
         CaptivePortalLoginHelper.handleEvent(detail);
         break;
       case 'inputmethod-update-layouts':
       case 'inputregistry-add':
       case 'inputregistry-remove':
         KeyboardHelper.handleEvent(detail);
         break;
-      case 'system-audiochannel-list':
-      case 'system-audiochannel-mute':
-      case 'system-audiochannel-volume':
-        SystemAppMozBrowserHelper.handleEvent(detail);
-        break;
       case 'do-command':
         DoCommandHelper.handleEvent(detail.cmd);
         break;
       case 'copypaste-do-command':
         Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser },
                                      'ask-children-to-execute-copypaste-command', detail.cmd);
         break;
       case 'add-permission':
@@ -1071,73 +1051,16 @@ var KeyboardHelper = {
       case 'inputregistry-remove':
         Keyboard.inputRegistryGlue.returnMessage(detail);
 
         break;
     }
   }
 };
 
-var SystemAppMozBrowserHelper = {
-  handleEvent: function systemAppMozBrowser_handleEvent(detail) {
-    let request;
-    let name;
-    switch (detail.type) {
-      case 'system-audiochannel-list':
-        let audioChannels = [];
-        shell.allowedAudioChannels.forEach(function(value, name) {
-          audioChannels.push(name);
-        });
-        SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
-          type: 'system-audiochannel-list',
-          audioChannels: audioChannels
-        });
-        break;
-      case 'system-audiochannel-mute':
-        name = detail.name;
-        let isMuted = detail.isMuted;
-        request = shell.allowedAudioChannels.get(name).setMuted(isMuted);
-        request.onsuccess = function() {
-          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
-            type: 'system-audiochannel-mute-onsuccess',
-            name: name,
-            isMuted: isMuted
-          });
-        };
-        request.onerror = function() {
-          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
-            type: 'system-audiochannel-mute-onerror',
-            name: name,
-            isMuted: isMuted
-          });
-        };
-        break;
-      case 'system-audiochannel-volume':
-        name = detail.name;
-        let volume = detail.volume;
-        request = shell.allowedAudioChannels.get(name).setVolume(volume);
-        request.onsuccess = function() {
-          sSystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
-            type: 'system-audiochannel-volume-onsuccess',
-            name: name,
-            volume: volume
-          });
-        };
-        request.onerror = function() {
-          SystemAppProxy._sendCustomEvent('mozSystemWindowChromeEvent', {
-            type: 'system-audiochannel-volume-onerror',
-            name: name,
-            volume: volume
-          });
-        };
-        break;
-    }
-  }
-};
-
 // This is the backend for Gaia's screenshot feature.  Gaia requests a
 // screenshot by sending a mozContentEvent with detail.type set to
 // 'take-screenshot'.  Then we take a screenshot and send a
 // mozChromeEvent with detail.type set to 'take-screenshot-success'
 // and detail.file set to the an image/png blob
 window.addEventListener('ContentStart', function ss_onContentStart() {
   let content = shell.contentBrowser.contentWindow;
   content.addEventListener('mozContentEvent', function ss_onMozContentEvent(e) {
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,26 +10,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"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,26 +10,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"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c72c9278ddc2f442d193474993d36e7f2cfb08c4"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,26 +10,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"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "b6ede3d0fdec5fc922e9ca3401e60db461bf705c", 
+        "git_revision": "a26eadc5e1133d5112b6cbc10badbb7670a1090f", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "220b45ec153f267a2efc58275a30a665a4ec9e57", 
+    "revision": "a99ff14b3258f49f5902775a5e3b849f3455714a", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-kk/sources.xml
@@ -10,26 +10,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"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -13,20 +13,20 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,26 +10,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"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="b6ede3d0fdec5fc922e9ca3401e60db461bf705c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="956700d9754349b630a34551750ae6353614b6aa"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3b9a47b517d345b8d98bc7f787b9a6c2f51ca75d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="1837d370a964a9719160c79155a07980f2ea4bdf"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -931,28 +931,33 @@ chatbox[customSize] {
   width: 350px; /* CHAT_WIDTH_OPEN_ALT in socialchat.xml */
 }
 
 #chat-window[customSize] {
   min-width: 350px;
 }
 
 chatbox[customSize="loopChatEnabled"] {
-  /* 325px as defined per UX */
-  height: 325px;
+  /* 430px as defined per UX */
+  height: 430px;
 }
 
 #chat-window[customSize="loopChatEnabled"] {
   /* 325px + 30px top bar height. */
   min-height: calc(325px + 30px);
 }
 
 chatbox[customSize="loopChatMessageAppended"] {
-  /* 400px as defined per UX */
-  height: 400px;
+  /* 430px as defined per UX */
+  height: 430px;
+}
+
+chatbox[customSize="loopChatDisabledMessageAppended"] {
+  /* 388px as defined per UX */
+  height: 388px;
 }
 
 #chat-window[customSize="loopChatMessageAppended"] {
   /* 445px + 30px top bar height. */
   min-height: calc(400px + 30px);
 }
 
 chatbox[minimized="true"] {
@@ -966,18 +971,18 @@ chatbar {
   max-height: 0;
 }
 
 .chatbar-innerbox {
   margin: -285px 0 0;
 }
 
 chatbar[customSize] > .chatbar-innerbox {
-  /* 425px to make room for the maximum custom-size chatbox; currently 'loopChatMessageAppended'. */
-  margin-top: -425px;
+  /* 450px to make room for the maximum custom-size chatbox; currently 'loopChatMessageAppended'. */
+  margin-top: -450px;
 }
 
 /* Apply crisp rendering for favicons at exactly 2dppx resolution */
 @media (resolution: 2dppx) {
   #social-sidebar-favico,
   .social-status-button,
   .chat-status-icon {
     image-rendering: -moz-crisp-edges;
--- a/browser/base/content/test/alerts/browser.ini
+++ b/browser/base/content/test/alerts/browser.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 support-files =
   file_dom_notifications.html
 
+[browser_notification_close.js]
 [browser_notification_do_not_disturb.js]
 [browser_notification_open_settings.js]
 [browser_notification_remove_permission.js]
 
 [browser_notification_tab_switching.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_close.js
@@ -0,0 +1,48 @@
+"use strict";
+
+let tab;
+let notification;
+let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+add_task(function* test_notificationClose() {
+  let pm = Services.perms;
+  pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: notificationURL
+  }, function* dummyTabTask(aBrowser) {
+    let win = aBrowser.contentWindow.wrappedJSObject;
+    notification = win.showNotification2();
+    yield BrowserTestUtils.waitForEvent(notification, "show");
+
+    info("Notification alert showing");
+
+    let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+    if (!alertWindow) {
+      ok(true, "Notifications don't use XUL windows on all platforms.");
+      notification.close();
+      return;
+    }
+
+    let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
+    is(alertCloseButton.localName, "toolbarbutton", "close button found");
+    let promiseBeforeUnloadEvent =
+      BrowserTestUtils.waitForEvent(alertWindow, "beforeunload");
+    let closedTime = alertWindow.Date.now();
+    alertCloseButton.click();
+    info("Clicked on close button");
+    let beforeUnloadEvent = yield promiseBeforeUnloadEvent;
+
+    ok(true, "Alert should close when the close button is clicked");
+    let currentTime = alertWindow.Date.now();
+    // The notification will self-close at 12 seconds, so this checks
+    // that the notification closed before the timeout.
+    ok(currentTime - closedTime < 5000,
+       "Close requested at " + closedTime + ", actually closed at " + currentTime);
+  });
+});
+
+add_task(function* cleanup() {
+  Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+});
--- a/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
+++ b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
@@ -59,17 +59,17 @@ function onAlertShowing() {
     notification.close();
     finish();
     return;
   }
   let doNotDisturbMenuItem = alertWindow.document.getElementById("doNotDisturbMenuItem");
   is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found");
   alertWindow.addEventListener("beforeunload", onAlertClosing);
   doNotDisturbMenuItem.click();
-  info("Clicked on do-not-disturb menuitem")
+  info("Clicked on do-not-disturb menuitem");
 }
 
 function onAlertClosing(event) {
   event.target.removeEventListener("beforeunload", onAlertClosing);
 
   ok(ALERT_SERVICE.manualDoNotDisturb, "Alert service should be disabled after clicking menuitem");
   let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
   notification2 = win.showNotification2();
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -194,35 +194,42 @@ extensions.registerAPI((extension, conte
             let gBrowser = browser.ownerDocument.defaultView.gBrowser;
             let tab = gBrowser.getTabForBrowser(browser);
             let tabId = TabManager.getId(tab);
             let [needed, changeInfo] = sanitize(extension, {status});
             fire(tabId, changeInfo, TabManager.convert(extension, tab));
           },
 
           onLocationChange(browser, webProgress, request, locationURI, flags) {
+            if (!webProgress.isTopLevel) {
+              return;
+            }
             let gBrowser = browser.ownerDocument.defaultView.gBrowser;
             let tab = gBrowser.getTabForBrowser(browser);
             let tabId = TabManager.getId(tab);
-            let [needed, changeInfo] = sanitize(extension, {url: locationURI.spec});
+            let [needed, changeInfo] = sanitize(extension, {
+              status: webProgress.isLoadingDocument ? "loading" : "complete",
+              url: locationURI.spec
+            });
             if (needed) {
               fire(tabId, changeInfo, TabManager.convert(extension, tab));
             }
           },
         };
 
         AllWindowEvents.addListener("progress", progressListener);
         AllWindowEvents.addListener("TabAttrModified", listener);
         AllWindowEvents.addListener("TabPinned", listener);
         AllWindowEvents.addListener("TabUnpinned", listener);
+
         return () => {
           AllWindowEvents.removeListener("progress", progressListener);
-          AllWindowEvents.addListener("TabAttrModified", listener);
-          AllWindowEvents.addListener("TabPinned", listener);
-          AllWindowEvents.addListener("TabUnpinned", listener);
+          AllWindowEvents.removeListener("TabAttrModified", listener);
+          AllWindowEvents.removeListener("TabPinned", listener);
+          AllWindowEvents.removeListener("TabUnpinned", listener);
         };
       }).api(),
 
       onReplaced: ignoreEvent(),
 
       onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
         let tabListener = event => {
           let tab = event.originalTarget;
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -525,17 +525,17 @@ global.AllWindowEvents = {
     if (type == "domwindowopened") {
       return WindowListManager.removeOpenListener(listener);
     } else if (type == "domwindowclosed") {
       return WindowListManager.removeCloseListener(listener);
     }
 
     let listeners = this._listeners.get(type);
     listeners.delete(listener);
-    if (listeners.length == 0) {
+    if (listeners.size == 0) {
       this._listeners.delete(type);
       if (this._listeners.size == 0) {
         WindowListManager.removeOpenListener(this.openListener);
       }
     }
 
     for (let window of WindowListManager.browserWindows()) {
       if (type == "progress") {
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,22 +1,25 @@
 [DEFAULT]
 support-files =
   head.js
   context.html
   ctxmenu-image.png
+  context_tabs_onUpdated_page.html
+  context_tabs_onUpdated_iframe.html
 
 [browser_ext_simple.js]
 [browser_ext_currentWindow.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_context.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_contextMenus.js]
 [browser_ext_getViews.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_update.js]
+[browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_windows_update.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_tab_runtimeConnect.js]
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -1,26 +1,8 @@
-function* focusWindow(win)
-{
-  let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-  if (fm.activeWindow == win) {
-    return;
-  }
-
-  let promise = new Promise(resolve => {
-    win.addEventListener("focus", function listener() {
-      win.removeEventListener("focus", listener, true);
-      resolve();
-    }, true);
-  });
-
-  win.focus();
-  yield promise;
-}
-
 function genericChecker()
 {
   var kind = "background";
   var path = window.location.pathname;
   if (path.indexOf("popup") != -1) {
     kind = "popup";
   } else if (path.indexOf("page") != -1) {
     kind = "page";
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
@@ -0,0 +1,78 @@
+add_task(function* () {
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  yield focusWindow(win1);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+      "content_scripts": [{
+        "matches": ["http://mochi.test/*/context_tabs_onUpdated_page.html"],
+        "js": ["content-script.js"],
+        "run_at": "document_start"
+      },],
+    },
+
+    background: function() {
+      var pageURL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";
+
+      var expectedSequence = [
+        { status: "loading" },
+        { status: "loading", url: pageURL },
+        { status: "complete" }
+      ];
+      var collectedSequence = [];
+
+      browser.tabs.onUpdated.addListener(function (tabId, updatedInfo) {
+        collectedSequence.push(updatedInfo);
+      });
+
+      browser.runtime.onMessage.addListener(function () {
+          if (collectedSequence.length !== expectedSequence.length) {
+            browser.test.assertEq(
+              JSON.stringify(expectedSequence),
+              JSON.stringify(collectedSequence),
+              "got unexpected number of updateInfo data"
+            );
+          } else {
+            for (var i = 0; i < expectedSequence.length; i++) {
+              browser.test.assertEq(
+                expectedSequence[i].status,
+                collectedSequence[i].status,
+                "check updatedInfo status"
+              );
+              if (expectedSequence[i].url || collectedSequence[i].url) {
+                browser.test.assertEq(
+                  expectedSequence[i].url,
+                  collectedSequence[i].url,
+                  "check updatedInfo url"
+                );
+              }
+            }
+          }
+
+          browser.test.notifyPass("tabs.onUpdated");
+      });
+
+      browser.tabs.create({ url: pageURL });
+    },
+    files: {
+      "content-script.js": `
+        window.addEventListener("message", function(evt) {
+          if (evt.data == "frame-updated") {
+            browser.runtime.sendMessage("load-completed");
+          }
+        }, true);
+      `,
+    }
+  });
+
+  yield Promise.all([
+    extension.startup(),
+    extension.awaitFinish("tabs.onUpdated")
+  ]);
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.closeWindow(win1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/context_tabs_onUpdated_iframe.html
@@ -0,0 +1,17 @@
+<html>
+  <body>
+    <h3>test iframe</h3>
+    <script>
+      window.onload = function() {
+        window.onhashchange = function() {
+          window.parent.postMessage("updated-iframe-url", "*");
+        }
+        // NOTE: without the this setTimeout the location change is not fired
+        // even without the "fire only for top level windows" fix
+        setTimeout(function() {
+          window.location.hash="updated-iframe-url";
+        }, 0);
+      }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html
@@ -0,0 +1,16 @@
+<html>
+  <body>
+    <h3>test page</h3>
+    <iframe src="about:blank"></iframe>
+    <script>
+      window.onmessage = function(evt) {
+        if (evt.data === "updated-iframe-url") {
+          window.postMessage("frame-updated", "*");
+        }
+      };
+      window.onload = function() {
+        document.querySelector('iframe').setAttribute("src", "context_tabs_onUpdated_iframe.html");
+      };
+    </script>
+  </body>
+</html>
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -1,7 +1,25 @@
 var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
 
 function makeWidgetId(id)
 {
   id = id.toLowerCase();
   return id.replace(/[^a-z0-9_-]/g, "_");
 }
+
+function* focusWindow(win)
+{
+  let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+  if (fm.activeWindow == win) {
+    return;
+  }
+
+  let promise = new Promise(resolve => {
+    win.addEventListener("focus", function listener() {
+      win.removeEventListener("focus", listener, true);
+      resolve();
+    }, true);
+  });
+
+  win.focus();
+  yield promise;
+}
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -525,23 +525,23 @@ body[platform="win"] .share-service-drop
   background-image: url("../img/icons-16x16.svg#add-hover");
 }
 
 .dropdown-menu-item:hover:active > .icon-add-share-service {
   background-image: url("../img/icons-16x16.svg#add-active");
 }
 
 .context-url-view-wrapper {
-  padding: 12px;
-  margin-bottom: 0.5em;
+  padding: 14px 15px;
   background-color: #dbf7ff;
 }
 
 .showing-room-name > .text-chat-entries > .text-chat-scroller > .context-url-view-wrapper {
   padding-top: 0;
+  margin-bottom: 0;
 }
 
 .room-context {
   background: #fff;
   position: absolute;
   left: 0;
   bottom: 0;
   width: 100%;
@@ -750,17 +750,17 @@ body[platform="win"] .share-service-drop
     flex-direction: row;
     margin: 0;
     width: 100%;
   }
 
   .media-wrapper > .focus-stream {
     width: 100%;
     /* A reasonable height */
-    height: 70%;
+    height: 55%;
   }
 
   .media-wrapper.receiving-screen-share > .focus-stream {
     height: 50%;
   }
 
   /* Temporarily slaved from .media-wrapper until we use it in more places
      to avoid affecting the conversation window on desktop. */
@@ -826,17 +826,17 @@ body[platform="win"] .share-service-drop
   .media-wrapper.receiving-screen-share > .remote > .conversation-toolbar,
   .media-wrapper.showing-local-streams.receiving-screen-share  > .remote > .conversation-toolbar {
     bottom: calc(30% + 1.5rem);
   }
 
 
   .desktop-room-wrapper > .media-layout > .media-wrapper > .text-chat-view {
     /* This is temp, to echo the .media-wrapper > .text-chat-view above */
-    height: 30%;
+    height: 45%;
   }
 
   .media-wrapper.receiving-screen-share > .screen {
     order: 1;
   }
 
   .media-wrapper.receiving-screen-share > .remote {
     /* Screen shares have remote & local video side-by-side on narrow screens */
@@ -925,17 +925,22 @@ body[platform="win"] .share-service-drop
   }
 
   .media-wrapper:not(.showing-remote-streams) > .focus-stream > .local ~ .conversation-toolbar {
     max-width: 100%;
   }
 
   .media-wrapper > .focus-stream {
     flex: 1 1 auto;
-    height: auto;
+    /* To keep the aspect ratio of video. Update accordingly with chatbox[customSize] width */
+    height: 263px;
+  }
+
+  .desktop-room-wrapper > .media-layout > .media-wrapper > .text-chat-view {
+    height: calc(100% - 263px);
   }
 }
 
 /* Text chat in styles */
 
 .text-chat-view {
   background: white;
 }
@@ -955,16 +960,20 @@ body[platform="win"] .share-service-drop
   margin: 0;
 }
 
 .text-chat-entry {
   /* aligns paragraph to side where reading starts from */
   text-align: start;
 }
 
+.text-chat-scroller div:nth-child(2) {
+  margin-top: .5em;
+}
+
 /* Sent text chat entries should be on the right */
 .text-chat-entry.sent {
   /* aligns paragraph to right side */
   justify-content: flex-end;
   margin-left: 0;
   margin-right: 5px;
 }
 
@@ -1203,29 +1212,26 @@ html[dir="rtl"] .text-chat-entry.receive
   }
 }
 
 /* e.g. very narrow widths similar to conversation window */
 @media screen and (max-width:350px) {
   .text-chat-view {
     display: flex;
     flex-flow: column nowrap;
-    /* 120px max-height of .text-chat-entries plus 40px of .text-chat-box */
-    max-height: 160px;
     /* 60px min-height of .text-chat-entries plus 40px of .text-chat-box */
     min-height: 100px;
     height: auto;
     /* Let the view be the minimum size it needs to be - don't flex to take up
        more. */
     flex: 0 0 auto !important;
   }
 
   .text-chat-entries {
     flex: 1 1 auto;
-    max-height: 120px;
     min-height: 60px;
   }
 
   .text-chat-view.text-chat-disabled {
     /* When we don't have text chat enabled, limit the view to the same height
        as the entries, to avoid unnecessary whitespace */
     max-height: 120px;
   }
--- a/browser/components/loop/content/shared/js/textChatStore.js
+++ b/browser/components/loop/content/shared/js/textChatStore.js
@@ -114,17 +114,21 @@ loop.store.TextChatStore = (function() {
       } else {
         newList.push(message);
       }
       this.setStoreState({ messageList: newList });
 
       // Notify MozLoopService if appropriate that a message has been appended
       // and it should therefore check if we need a different sized window or not.
       if (message.contentType !== CHAT_CONTENT_TYPES.ROOM_NAME) {
-        window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
+        if (this._storeState.textChatEnabled) {
+          window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
+        } else {
+          window.dispatchEvent(new CustomEvent("LoopChatDisabledMessageAppended"));
+        }
       }
     },
 
     /**
      * Handles received text chat messages.
      *
      * @param {sharedActions.ReceivedTextChatMessage} actionData
      */
--- a/browser/components/loop/modules/MozLoopService.jsm
+++ b/browser/components/loop/modules/MozLoopService.jsm
@@ -958,16 +958,17 @@ var MozLoopServiceInternal = {
         window.addEventListener("socialFrameHide", socialFrameChanged.bind(null, "Loop:ChatWindowHidden"));
         window.addEventListener("socialFrameShow", socialFrameChanged.bind(null, "Loop:ChatWindowShown"));
         window.addEventListener("socialFrameDetached", socialFrameChanged.bind(null, "Loop:ChatWindowDetached"));
         window.addEventListener("socialFrameAttached", socialFrameChanged.bind(null, "Loop:ChatWindowAttached"));
         window.addEventListener("unload", socialFrameChanged.bind(null, "Loop:ChatWindowClosed"));
 
         const kSizeMap = {
           LoopChatEnabled: "loopChatEnabled",
+          LoopChatDisabledMessageAppended: "loopChatDisabledMessageAppended",
           LoopChatMessageAppended: "loopChatMessageAppended"
         };
 
         function onChatEvent(ev) {
           // When the chat box or messages are shown, resize the panel or window
           // to be slightly higher to accomodate them.
           let customSize = kSizeMap[ev.type];
           let currSize = chatbox.getAttribute("customSize");
@@ -976,16 +977,17 @@ var MozLoopServiceInternal = {
           if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
             chatbox.setAttribute("customSize", customSize);
             chatbox.parentNode.setAttribute("customSize", customSize);
           }
         }
 
         window.addEventListener("LoopChatEnabled", onChatEvent);
         window.addEventListener("LoopChatMessageAppended", onChatEvent);
+        window.addEventListener("LoopChatDisabledMessageAppended", onChatEvent);
 
         injectLoopAPI(window);
 
         let ourID = window.QueryInterface(Ci.nsIInterfaceRequestor)
             .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
 
         let onPCLifecycleChange = (pc, winID, type) => {
           if (winID != ourID) {
--- a/browser/components/loop/test/shared/textChatStore_test.js
+++ b/browser/components/loop/test/shared/textChatStore_test.js
@@ -91,16 +91,17 @@ describe("loop.store.TextChatStore", fun
         contentType: "invalid type",
         message: "Hi"
       });
 
       expect(store.getStoreState("messageList").length).eql(0);
     });
 
     it("should dispatch a LoopChatMessageAppended event", function() {
+      store.setStoreState({ textChatEnabled: true });
       store.receivedTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: "Hello!"
       });
 
       sinon.assert.calledOnce(window.dispatchEvent);
       sinon.assert.calledWithExactly(window.dispatchEvent,
         new CustomEvent("LoopChatMessageAppended"));
@@ -136,16 +137,17 @@ describe("loop.store.TextChatStore", fun
         message: messageData.message,
         extraData: undefined,
         sentTimestamp: "2015-06-24T23:58:53.848Z",
         receivedTimestamp: "2015-06-24T23:58:53.848Z"
       }]);
     });
 
     it("should dipatch a LoopChatMessageAppended event", function() {
+      store.setStoreState({ textChatEnabled: true });
       store.sendTextChatMessage({
         contentType: CHAT_CONTENT_TYPES.TEXT,
         message: "Hello!"
       });
 
       sinon.assert.calledOnce(window.dispatchEvent);
       sinon.assert.calledWithExactly(window.dispatchEvent,
         new CustomEvent("LoopChatMessageAppended"));
@@ -241,16 +243,33 @@ describe("loop.store.TextChatStore", fun
         receivedTimestamp: undefined,
         extraData: {
           location: "http://wonderful.invalid2",
           thumbnail: "fake2"
         }
       }]);
     });
 
+    it("should dispatch a LoopChatDisabledMessageAppended event", function() {
+      store.setStoreState({ textChatEnabled: false });
+      store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
+        roomName: "Let's share!",
+        roomUrl: "fake",
+        roomContextUrls: [{
+          description: "A wonderful event2",
+          location: "http://wonderful.invalid2",
+          thumbnail: "fake2"
+        }]
+      }));
+
+      sinon.assert.calledOnce(window.dispatchEvent);
+      sinon.assert.calledWithExactly(window.dispatchEvent,
+        new CustomEvent("LoopChatDisabledMessageAppended"));
+    });
+
     it("should not dispatch a LoopChatMessageAppended event", function() {
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
         roomName: "Let's share!",
         roomUrl: "fake"
       }));
 
       sinon.assert.notCalled(window.dispatchEvent);
     });
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -893,17 +893,17 @@
                   dispatcher: dispatcher, 
                   failureReason: FAILURE_DETAILS.UNKNOWN, 
                   mozLoop: navigator.mozLoop})
               )
             )
           ), 
 
           React.createElement(Section, {name: "DesktopRoomConversationView"}, 
-            React.createElement(FramedExample, {height: 398, 
+            React.createElement(FramedExample, {height: 448, 
                            onContentsRendered: invitationRoomStore.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)", 
                            width: 348}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   chatWindowDetached: false, 
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
@@ -927,17 +927,17 @@
                   roomData: {}, 
                   savingContext: false, 
                   show: true}
                   )
               )
             ), 
 
             React.createElement(FramedExample, {dashed: true, 
-                           height: 398, 
+                           height: 448, 
                            onContentsRendered: desktopRoomStoreLoading.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation (loading)", 
                            width: 348}, 
               /* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */
               React.createElement("div", {className: "fx-embedded overflow-hidden"}, 
                 React.createElement(DesktopRoomConversationView, {
                   chatWindowDetached: false, 
@@ -947,17 +947,17 @@
                   onCallTerminated: function() {}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                   roomStore: desktopRoomStoreLoading})
               )
             ), 
 
             React.createElement(FramedExample, {dashed: true, 
-                           height: 398, 
+                           height: 448, 
                            onContentsRendered: roomStore.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation", 
                            width: 348}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   chatWindowDetached: false, 
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
@@ -1001,33 +1001,33 @@
                   onCallTerminated: function() {}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomState: ROOM_STATES.HAS_PARTICIPANTS, 
                   roomStore: desktopRoomStoreLarge})
               )
             ), 
 
             React.createElement(FramedExample, {dashed: true, 
-                           height: 398, 
+                           height: 448, 
                            onContentsRendered: desktopLocalFaceMuteRoomStore.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation local face-mute", 
                            width: 348}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   chatWindowDetached: false, 
                   dispatcher: dispatcher, 
                   mozLoop: navigator.mozLoop, 
                   onCallTerminated: function() {}, 
                   remotePosterUrl: "sample-img/video-screen-remote.png", 
                   roomStore: desktopLocalFaceMuteRoomStore})
               )
             ), 
 
             React.createElement(FramedExample, {dashed: true, 
-                           height: 398, 
+                           height: 448, 
                            onContentsRendered: desktopRemoteFaceMuteRoomStore.activeRoomStore.forcedUpdate, 
                            summary: "Desktop room conversation remote face-mute", 
                            width: 348}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(DesktopRoomConversationView, {
                   chatWindowDetached: false, 
                   dispatcher: dispatcher, 
                   localPosterUrl: "sample-img/video-screen-local.png", 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -893,17 +893,17 @@
                   dispatcher={dispatcher}
                   failureReason={FAILURE_DETAILS.UNKNOWN}
                   mozLoop={navigator.mozLoop} />
               </div>
             </FramedExample>
           </Section>
 
           <Section name="DesktopRoomConversationView">
-            <FramedExample height={398}
+            <FramedExample height={448}
                            onContentsRendered={invitationRoomStore.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation (invitation, text-chat inclusion/scrollbars don't happen in real client)"
                            width={348}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   chatWindowDetached={false}
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
@@ -927,17 +927,17 @@
                   roomData={{}}
                   savingContext={false}
                   show={true}
                   />
               </div>
             </FramedExample>
 
             <FramedExample dashed={true}
-                           height={398}
+                           height={448}
                            onContentsRendered={desktopRoomStoreLoading.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation (loading)"
                            width={348}>
               {/* Hide scrollbars here. Rotating loading div overflows and causes
                scrollbars to appear */}
               <div className="fx-embedded overflow-hidden">
                 <DesktopRoomConversationView
                   chatWindowDetached={false}
@@ -947,17 +947,17 @@
                   onCallTerminated={function() {}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS}
                   roomStore={desktopRoomStoreLoading} />
               </div>
             </FramedExample>
 
             <FramedExample dashed={true}
-                           height={398}
+                           height={448}
                            onContentsRendered={roomStore.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation"
                            width={348}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   chatWindowDetached={false}
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
@@ -1001,33 +1001,33 @@
                   onCallTerminated={function() {}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomState={ROOM_STATES.HAS_PARTICIPANTS}
                   roomStore={desktopRoomStoreLarge} />
               </div>
             </FramedExample>
 
             <FramedExample dashed={true}
-                           height={398}
+                           height={448}
                            onContentsRendered={desktopLocalFaceMuteRoomStore.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation local face-mute"
                            width={348}>
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   chatWindowDetached={false}
                   dispatcher={dispatcher}
                   mozLoop={navigator.mozLoop}
                   onCallTerminated={function() {}}
                   remotePosterUrl="sample-img/video-screen-remote.png"
                   roomStore={desktopLocalFaceMuteRoomStore} />
               </div>
             </FramedExample>
 
             <FramedExample dashed={true}
-                           height={398}
+                           height={448}
                            onContentsRendered={desktopRemoteFaceMuteRoomStore.activeRoomStore.forcedUpdate}
                            summary="Desktop room conversation remote face-mute"
                            width={348} >
               <div className="fx-embedded">
                 <DesktopRoomConversationView
                   chatWindowDetached={false}
                   dispatcher={dispatcher}
                   localPosterUrl="sample-img/video-screen-local.png"
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2664,30 +2664,46 @@ ContentPermissionPrompt.prototype = {
 
     this._showPrompt(aRequest, message, "geo", actions, "geolocation",
                      "geo-notification-icon", options);
   },
 
   _promptWebNotifications : function(aRequest) {
     var message = gBrowserBundle.GetStringFromName("webNotifications.receiveFromSite");
 
-    var actions = [
-      {
-        stringId: "webNotifications.alwaysReceive",
-        action: Ci.nsIPermissionManager.ALLOW_ACTION,
-        expireType: null,
-        callback: function() {},
-      },
-      {
-        stringId: "webNotifications.neverShow",
-        action: Ci.nsIPermissionManager.DENY_ACTION,
-        expireType: null,
-        callback: function() {},
-      },
-    ];
+    var actions;
+
+    var browser = this._getBrowserForRequest(aRequest);
+    // Only show "allow for session" in PB mode, we don't
+    // support "allow for session" in non-PB mode.
+    if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
+      actions = [
+        {
+          stringId: "webNotifications.receiveForSession",
+          action: Ci.nsIPermissionManager.ALLOW_ACTION,
+          expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+          callback: function() {},
+        }
+      ];
+    } else {
+      actions = [
+        {
+          stringId: "webNotifications.alwaysReceive",
+          action: Ci.nsIPermissionManager.ALLOW_ACTION,
+          expireType: null,
+          callback: function() {},
+        },
+        {
+          stringId: "webNotifications.neverShow",
+          action: Ci.nsIPermissionManager.DENY_ACTION,
+          expireType: null,
+          callback: function() {},
+        },
+      ];
+    }
 
     var options = {
       learnMoreURL: Services.urlFormatter.formatURLPref("browser.push.warning.infoURL"),
     };
 
     this._showPrompt(aRequest, message, "desktop-notification", actions,
                      "web-notifications",
                      "web-notifications-notification-icon", options);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -370,16 +370,18 @@ geolocation.shareLocation=Share Location
 geolocation.shareLocation.accesskey=a
 geolocation.alwaysShareLocation=Always Share Location
 geolocation.alwaysShareLocation.accesskey=A
 geolocation.neverShareLocation=Never Share Location
 geolocation.neverShareLocation.accesskey=N
 geolocation.shareWithSite2=Would you like to share your location with this site?
 geolocation.shareWithFile2=Would you like to share your location with this file?
 
+webNotifications.receiveForSession=Receive for this session
+webNotifications.receiveForSession.accesskey=s
 webNotifications.alwaysReceive=Always Receive Notifications
 webNotifications.alwaysReceive.accesskey=A
 webNotifications.neverShow=Always Block Notifications
 webNotifications.neverShow.accesskey=N
 webNotifications.receiveFromSite=Would you like to receive notifications from this site?
 
 # Pointer lock UI
 
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -43,19 +43,18 @@ mozNameTemplate=%1$S %2$S
 # LOCALIZATION NOTE (mozstdName, etc.): These labels appear in the tracking
 # protection block lists dialog, mozNameTemplate is used to create the final
 # string. Note that in the future these two strings (name, desc) could be
 # displayed on two different lines.
 mozstdName=Disconnect.me basic protection (Recommended).
 mozstdDesc=Allows some trackers so websites function properly.
 mozfullName=Disconnect.me strict protection.
 mozfullDesc=Blocks known trackers. Some sites may not function properly.
-# LOCALIZATION NOTE (blocklistChangeRequiresRestart, restartTitle): %S = brandShortName
+# LOCALIZATION NOTE (blocklistChangeRequiresRestart): %S = brandShortName
 blocklistChangeRequiresRestart=%S must restart to change block lists.
-shouldRestartTitle=Restart %S
 
 #### Master Password
 
 pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
 pw_change_failed_title=Password Change Failed
 
 #### Fonts
 
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -24,16 +24,17 @@
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
     // Rules from the mozilla plugin
     "mozilla/components-imports": 1,
     "mozilla/import-headjs-globals": 1,
     "mozilla/mark-test-function-used": 1,
+    "mozilla/var-only-at-top-level": 1,
 
     // Disallow using variables outside the blocks they are defined (especially
     // since only let and const are used, see "no-var").
     "block-scoped-var": 2,
     // Enforce one true brace style (opening brace on the same line) and avoid
     // start and end braces on the same line.
     "brace-style": [2, "1tbs", {"allowSingleLine": false}],
     // Require camel case names
@@ -265,18 +266,19 @@
     "no-unneeded-ternary": 2,
     // Disallow unreachable statements after a return, throw, continue, or break
     // statement.
     "no-unreachable": 2,
     // Disallow declaration of variables that are not used in the code
     "no-unused-vars": 2,
     // Allow using variables before they are defined.
     "no-use-before-define": 0,
-    // Require let or const instead of var.
-    "no-var": 2,
+    // We use var-only-at-top-level instead of no-var as we allow top level
+    // vars.
+    "no-var": 0,
     // Allow using TODO/FIXME comments.
     "no-warning-comments": 0,
     // Disallow use of the with statement.
     "no-with": 2,
     // Don't require method and property shorthand syntax for object literals.
     // We use this in the code a lot, but not consistently, and this seems more
     // like something to check at code review time.
     "object-shorthand": 0,
--- a/devtools/client/memory/components/heap.js
+++ b/devtools/client/memory/components/heap.js
@@ -4,17 +4,19 @@
 
 const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const Tree = createFactory(require("./tree"));
 const TreeItem = createFactory(require("./tree-item"));
 const { getSnapshotStatusText } = require("../utils");
 const { snapshotState: states } = require("../constants");
 const { snapshot: snapshotModel } = require("../models");
 const TAKE_SNAPSHOT_TEXT = "Take snapshot";
-const TREE_ROW_HEIGHT = 10;
+// If HEAP_TREE_ROW_HEIGHT changes, be sure to change `var(--heap-tree-row-height)`
+// in `devtools/client/themes/memory.css`
+const HEAP_TREE_ROW_HEIGHT = 14;
 
 /**
  * Creates a hash map mapping node IDs to its parent node.
  *
  * @param {CensusTreeNode} node
  * @param {Object<number, CensusTreeNode>} aggregator
  *
  * @return {Object<number, CensusTreeNode>}
@@ -40,17 +42,17 @@ function createTreeProperties (census) {
   return {
     // getParent only used for focusing parents when child selected;
     // handle this later?
     getParent: node => map(node.id),
     getChildren: node => node.children || [],
     renderItem: (item, depth, focused, arrow) => new TreeItem({ item, depth, focused, arrow }),
     getRoots: () => census.children,
     getKey: node => node.id,
-    itemHeight: TREE_ROW_HEIGHT,
+    itemHeight: HEAP_TREE_ROW_HEIGHT,
   };
 }
 
 /**
  * Main view for the memory tool -- contains several panels for different states;
  * an initial state of only a button to take a snapshot, loading states, and the
  * heap view tree.
  */
@@ -80,16 +82,23 @@ const Heap = module.exports = createClas
       case states.READING:
       case states.READ:
       case states.SAVING_CENSUS:
         pane = dom.div({ className: "heap-view-panel", "data-state": state },
           getSnapshotStatusText(snapshot));
         break;
       case states.SAVED_CENSUS:
         pane = dom.div({ className: "heap-view-panel", "data-state": "loaded" },
+          dom.div({ className: "header" },
+            dom.span({ className: "heap-tree-item-bytes" }, "Bytes"),
+            dom.span({ className: "heap-tree-item-count" }, "Count"),
+            dom.span({ className: "heap-tree-item-total-bytes" }, "Total Bytes"),
+            dom.span({ className: "heap-tree-item-total-count" }, "Total Count"),
+            dom.span({ className: "heap-tree-item-name" }, "Name")
+          ),
           Tree(createTreeProperties(snapshot.census))
         );
         break;
     }
 
     return (
       dom.div({ id: "heap-view", "data-state": state }, pane)
     )
--- a/devtools/client/memory/components/tree-item.js
+++ b/devtools/client/memory/components/tree-item.js
@@ -10,18 +10,20 @@ const INDENT = 10;
  * (▶). When its node has no children, it is hidden.
  */
 const TreeItem = module.exports = createClass({
   displayName: "tree-item",
 
   render() {
     let { item, depth, arrow, focused } = this.props;
 
-    return dom.div({ className: "heap-tree-item", style: { marginLeft: depth * INDENT }},
-      arrow,
-      dom.span({ className: "heap-tree-item-name" }, item.name),
+    return dom.div({ className: "heap-tree-item" },
       dom.span({ className: "heap-tree-item-bytes" }, item.bytes),
       dom.span({ className: "heap-tree-item-count" }, item.count),
       dom.span({ className: "heap-tree-item-total-bytes" }, item.totalBytes),
-      dom.span({ className: "heap-tree-item-total-count" }, item.totalCount)
+      dom.span({ className: "heap-tree-item-total-count" }, item.totalCount),
+      dom.span({ className: "heap-tree-item-name", style: { marginLeft: depth * INDENT }},
+        arrow,
+        item.name
+      )
     );
   }
 });
--- a/devtools/client/performance/docs/markers.md
+++ b/devtools/client/performance/docs/markers.md
@@ -136,16 +136,24 @@ A marker generated via `console.time()` 
 
 ## TimeStamp
 
 A marker generated via `console.timeStamp(label)`.
 
 * DOMString causeName - the label passed into `console.timeStamp(label)`
   if passed in.
 
+## document::DOMContentLoaded
+
+A marker generated when the DOMContentLoaded event is fired.
+
+## document::Load
+
+A marker generated when the document's "load" event is fired.
+
 ## Parse HTML
 
 ## Parse XML
 
 ## Worker
 
 Emitted whenever there's an operation dealing with Workers (any kind of worker,
 Web Workers, Service Workers etc.). Currently there are 4 types of operations
--- a/devtools/client/performance/modules/markers.js
+++ b/devtools/client/performance/modules/markers.js
@@ -86,16 +86,26 @@ const TIMELINE_BLUEPRINT = {
 
   /* Group 1 - JS */
   "DOMEvent": {
     group: 1,
     colorName: "graphs-yellow",
     label: L10N.getStr("marker.label.domevent"),
     fields: Formatters.DOMEventFields,
   },
+  "document::DOMContentLoaded": {
+    group: 1,
+    colorName: "graphs-full-red",
+    label: "DOMContentLoaded"
+  },
+  "document::Load": {
+    group: 1,
+    colorName: "graphs-full-blue",
+    label: "Load"
+  },
   "Javascript": {
     group: 1,
     colorName: "graphs-yellow",
     label: Formatters.JSLabel,
     fields: Formatters.JSFields
   },
   "Parse HTML": {
     group: 1,
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -51,16 +51,17 @@ skip-if = true # Bug 1161817
 [browser_perf-legacy-front-06.js]
 [browser_perf-legacy-front-07.js]
 [browser_perf-legacy-front-08.js]
 [browser_perf-legacy-front-09.js]
 [browser_perf-loading-01.js]
 [browser_perf-loading-02.js]
 [browser_perf-marker-details-01.js]
 skip-if = os == 'linux' # Bug 1172120
+[browser_perf-markers-docload.js]
 [browser_perf-options-01.js]
 [browser_perf-options-02.js]
 [browser_perf-options-03.js]
 [browser_perf-options-invert-call-tree-01.js]
 [browser_perf-options-invert-call-tree-02.js]
 [browser_perf-options-invert-flame-graph-01.js]
 [browser_perf-options-invert-flame-graph-02.js]
 [browser_perf-options-flatten-tree-recursion-01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/browser_perf-markers-docload.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the sidebar is updated with "DOMContentLoaded" and "load" markers.
+ */
+
+function* spawnTest() {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { PerformanceController } = panel.panelWin;
+
+  loadFrameScripts();
+
+  yield startRecording(panel);
+  ok(true, "Recording has started.");
+
+  evalInDebuggee("document.location.reload()");
+
+  yield waitUntil(() => {
+    // Wait until we get the necessary markers.
+    let markers = PerformanceController.getCurrentRecording().getMarkers();
+    if (!markers.some(m => m.name == "document::DOMContentLoaded") ||
+        !markers.some(m => m.name == "document::Load")) {
+      return false;
+    }
+
+    ok(markers.filter(m => m.name == "document::DOMContentLoaded").length == 1,
+      "There should only be one `DOMContentLoaded` marker.");
+    ok(markers.filter(m => m.name == "document::Load").length == 1,
+      "There should only be one `load` marker.");
+
+    return true;
+  });
+
+  yield stopRecording(panel);
+  ok(true, "Recording has ended.");
+
+  yield teardown(panel);
+  finish();
+}
+
+/**
+ * Takes a string `script` and evaluates it directly in the content
+ * in potentially a different process.
+ */
+function evalInDebuggee (script) {
+  let { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
+  let deferred = Promise.defer();
+
+  if (!mm) {
+    throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
+  }
+
+  let id = generateUUID().toString();
+  mm.sendAsyncMessage("devtools:test:eval", { script: script, id: id });
+  mm.addMessageListener("devtools:test:eval:response", handler);
+
+  function handler ({ data }) {
+    if (id !== data.id) {
+      return;
+    }
+
+    mm.removeMessageListener("devtools:test:eval:response", handler);
+    deferred.resolve(data.value);
+  }
+
+  return deferred.promise;
+}
--- a/devtools/client/shared/test/browser_filter-editor-01.js
+++ b/devtools/client/shared/test/browser_filter-editor-01.js
@@ -28,9 +28,35 @@ add_task(function *() {
      "setCssValue should work for spaced values");
 
   info("Test parsing of string-typed values");
   widget.setCssValue("drop-shadow( 2px  1px 5px black) url( example.svg#filter )");
 
   is(widget.getCssValue(),
      "drop-shadow(2px  1px 5px black) url(example.svg#filter)",
      "setCssValue should work for string-typed values");
+
+  info("Test parsing of mixed-case function names");
+  widget.setCssValue("BLUR(2px) Contrast(200%) Drop-Shadow(2px 1px 5px Black)");
+  is(widget.getCssValue(),
+     "BLUR(2px) Contrast(200%) Drop-Shadow(2px 1px 5px Black)",
+     "setCssValue should work for mixed-case function names");
+
+  info("Test parsing of invalid filter value");
+  widget.setCssValue("totallyinvalid");
+  is(widget.getCssValue(), "none",
+     "setCssValue should turn completely invalid value to 'none'");
+
+  info("Test parsing of invalid function argument");
+  widget.setCssValue("blur('hello')");
+  is(widget.getCssValue(), "blur(0px)",
+     "setCssValue should replace invalid function argument with default");
+
+  info("Test parsing of invalid function argument #2");
+  widget.setCssValue("drop-shadow(whatever)");
+  is(widget.getCssValue(), "drop-shadow()",
+     "setCssValue should replace invalid drop-shadow argument with empty string");
+
+  info("Test parsing of mixed invalid argument");
+  widget.setCssValue("contrast(5%) whatever invert('xxx')");
+  is(widget.getCssValue(), "contrast(5%) invert(0%)",
+     "setCssValue should handle multiple errors");
 });
--- a/devtools/client/shared/test/browser_outputparser.js
+++ b/devtools/client/shared/test/browser_outputparser.js
@@ -17,16 +17,17 @@ add_task(function*() {
 function* performTest() {
   let [host, , doc] = yield createHost("bottom", "data:text/html," +
     "<h1>browser_outputParser.js</h1><div></div>");
 
   let parser = new OutputParser(doc);
   testParseCssProperty(doc, parser);
   testParseCssVar(doc, parser);
   testParseURL(doc, parser);
+  testParseFilter(doc, parser);
 
   host.destroy();
 }
 
 // Class name used in color swatch.
 var COLOR_TEST_CLASS = "test-class";
 
 // Create a new CSS color-parsing test.  |name| is the name of the CSS
@@ -244,8 +245,18 @@ function testParseURL(doc, parser) {
         "target=\"_blank\">something.jpg</a>" +
         expectedTrailer;
 
     is(target.innerHTML, expected, test.desc);
 
     target.innerHTML = "";
   }
 }
+
+function testParseFilter(doc, parser) {
+  let frag = parser.parseCssProperty("filter", "something invalid", {
+    filterSwatchClass: "test-filterswatch"
+  });
+
+  let swatchCount = frag.querySelectorAll(".test-filterswatch").length;
+  is(swatchCount, 1, "filter swatch was created");
+}
+
--- a/devtools/client/shared/widgets/FilterWidget.js
+++ b/devtools/client/shared/widgets/FilterWidget.js
@@ -5,25 +5,31 @@
 "use strict";
 
 /**
   * This is a CSS Filter Editor widget used
   * for Rule View's filter swatches
   */
 
 const EventEmitter = require("devtools/shared/event-emitter");
-const { Cu } = require("chrome");
-const { ViewHelpers } = Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm", {});
+const { Cu, Cc, Ci } = require("chrome");
+const { ViewHelpers } =
+      Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm",
+                {});
 const STRINGS_URI = "chrome://browser/locale/devtools/filterwidget.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const {cssTokenizer} = require("devtools/client/shared/css-parsing-utils");
 
 loader.lazyGetter(this, "asyncStorage",
                   () => require("devtools/shared/async-storage"));
 
+loader.lazyGetter(this, "DOMUtils", () => {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
+
 const DEFAULT_FILTER_TYPE = "length";
 const UNIT_MAPPING = {
   percentage: "%",
   length: "px",
   angle: "deg",
   string: ""
 };
 
@@ -302,34 +308,37 @@ CSSFilterEditorWidget.prototype = {
     // the value can be incremented/decremented simply.
     // For other types of filters (e.g. drop-shadow) we need to check
     // if the keypress happened close to a number first.
     if (filter.unit) {
       let startValue = parseFloat(e.target.value);
       let value = startValue + direction * multiplier;
 
       const [min, max] = this._definition(filter.name).range;
-      value = value < min ? min :
-              value > max ? max : value;
+      if (value < min) {
+        value = min;
+      } else if (value > max) {
+        value = max;
+      }
 
       input.value = fixFloat(value);
 
       this.updateValueAt(index, value);
     } else {
       let selectionStart = input.selectionStart;
       let num = getNeighbourNumber(input.value, selectionStart);
       if (!num) {
         return;
       }
 
       let {start, end, value} = num;
 
       let split = input.value.split("");
-      let computed = fixFloat(value + direction * multiplier),
-          dotIndex = computed.indexOf(".0");
+      let computed = fixFloat(value + direction * multiplier);
+      let dotIndex = computed.indexOf(".0");
       if (dotIndex > -1) {
         computed = computed.slice(0, -2);
 
         selectionStart = selectionStart > start + dotIndex ?
                                           start + dotIndex :
                                           selectionStart;
       }
       split.splice(start, end - start, computed);
@@ -338,20 +347,20 @@ CSSFilterEditorWidget.prototype = {
       input.value = value;
       this.updateValueAt(index, value);
       input.setSelectionRange(selectionStart, selectionStart);
     }
     e.preventDefault();
   },
 
   _input: function(e) {
-    let filterEl = e.target.closest(".filter"),
-        index = this._getFilterElementIndex(filterEl),
-        filter = this.filters[index],
-        def = this._definition(filter.name);
+    let filterEl = e.target.closest(".filter");
+    let index = this._getFilterElementIndex(filterEl);
+    let filter = this.filters[index];
+    let def = this._definition(filter.name);
 
     if (def.type !== "string") {
       e.target.value = fixFloat(e.target.value);
     }
     this.updateValueAt(index, e.target.value);
   },
 
   _mouseDown: function(e) {
@@ -361,19 +370,19 @@ CSSFilterEditorWidget.prototype = {
     if (e.target.tagName.toLowerCase() === "i") {
       this.isReorderingFilter = true;
       filterEl.startingY = e.pageY;
       filterEl.classList.add("dragging");
 
       this.el.classList.add("dragging");
     // label-dragging
     } else if (e.target.classList.contains("devtools-draglabel")) {
-      let label = e.target,
-          input = filterEl.querySelector("input"),
-          index = this._getFilterElementIndex(filterEl);
+      let label = e.target;
+      let input = filterEl.querySelector("input");
+      let index = this._getFilterElementIndex(filterEl);
 
       this._dragging = {
         index, label, input,
         startX: e.pageX
       };
 
       this.isDraggingLabel = true;
     }
@@ -381,29 +390,17 @@ CSSFilterEditorWidget.prototype = {
 
   _addButtonClick: function() {
     const select = this.filterSelect;
     if (!select.value) {
       return;
     }
 
     const key = select.value;
-    const def = this._definition(key);
-    // UNIT_MAPPING[string] is an empty string (falsy), so
-    // using || doesn't work here
-    const unitLabel = typeof UNIT_MAPPING[def.type] === "undefined" ?
-                             UNIT_MAPPING[DEFAULT_FILTER_TYPE] :
-                             UNIT_MAPPING[def.type];
-
-    // string-type filters have no default value but a placeholder instead
-    if (!unitLabel) {
-      this.add(key);
-    } else {
-      this.add(key, def.range[0] + unitLabel);
-    }
+    this.add(key, null);
 
     this.render();
   },
 
   _removeButtonClick: function(e) {
     const isRemoveButton = e.target.classList.contains("remove-button");
     if (!isRemoveButton) {
       return;
@@ -418,34 +415,37 @@ CSSFilterEditorWidget.prototype = {
     if (this.isReorderingFilter) {
       this._dragFilterElement(e);
     } else if (this.isDraggingLabel) {
       this._dragLabel(e);
     }
   },
 
   _dragFilterElement: function(e) {
-    const rect = this.filtersList.getBoundingClientRect(),
-          top = e.pageY - LIST_PADDING,
-          bottom = e.pageY + LIST_PADDING;
+    const rect = this.filtersList.getBoundingClientRect();
+    let top = e.pageY - LIST_PADDING;
+    let bottom = e.pageY + LIST_PADDING;
     // don't allow dragging over top/bottom of list
     if (top < rect.top || bottom > rect.bottom) {
       return;
     }
 
     const filterEl = this.filtersList.querySelector(".dragging");
 
     const delta = e.pageY - filterEl.startingY;
     filterEl.style.top = delta + "px";
 
     // change is the number of _steps_ taken from initial position
     // i.e. how many elements we have passed
     let change = delta / LIST_ITEM_HEIGHT;
-    change = change > 0 ? Math.floor(change) :
-             change < 0 ? Math.ceil(change) : change;
+    if (change > 0) {
+      change = Math.floor(change);
+    } else if (change < 0) {
+      change = Math.ceil(change);
+    }
 
     const children = this.filtersList.children;
     const index = [...children].indexOf(filterEl);
     const destination = index + change;
 
     // If we're moving out, or there's no change at all, stop and return
     if (destination >= children.length || destination < 0 || change === 0) {
       return;
@@ -484,18 +484,21 @@ CSSFilterEditorWidget.prototype = {
 
     dragging.lastX = e.pageX;
     const delta = e.pageX - dragging.startX;
     const startValue = parseFloat(input.value);
     let value = startValue + delta * multiplier;
 
     const filter = this.filters[dragging.index];
     const [min, max] = this._definition(filter.name).range;
-    value = value < min ? min :
-            value > max ? max : value;
+    if (value < min) {
+      value = min;
+    } else if (value > max) {
+      value = max;
+    }
 
     input.value = fixFloat(value);
 
     dragging.startX = e.pageX;
 
     this.updateValueAt(dragging.index, value);
   },
 
@@ -546,18 +549,18 @@ CSSFilterEditorWidget.prototype = {
   _togglePresets: function() {
     this.el.classList.toggle("show-presets");
     this.emit("render");
   },
 
   _savePreset: function(e) {
     e.preventDefault();
 
-    let name = this.addPresetInput.value,
-        value = this.getCssValue();
+    let name = this.addPresetInput.value;
+    let value = this.getCssValue();
 
     if (!name || !value || value === "none") {
       this.emit("preset-save-error");
       return;
     }
 
     this.getPresets().then(presets => {
       let index = presets.findIndex(preset => preset.name === name);
@@ -588,19 +591,19 @@ CSSFilterEditorWidget.prototype = {
 
     let base = this._filterItemMarkup;
 
     for (let filter of this.filters) {
       const def = this._definition(filter.name);
 
       let el = base.cloneNode(true);
 
-      let [name, value] = el.children,
-          label = name.children[1],
-          [input, unitPreview] = value.children;
+      let [name, value] = el.children;
+      let label = name.children[1];
+      let [input, unitPreview] = value.children;
 
       let min, max;
       if (def.range) {
         [min, max] = def.range;
       }
 
       label.textContent = filter.name;
       input.value = filter.value;
@@ -610,21 +613,21 @@ CSSFilterEditorWidget.prototype = {
         case "angle":
         case "length":
           input.type = "number";
           input.min = min;
           if (max !== Infinity) {
             input.max = max;
           }
           input.step = "0.1";
-        break;
+          break;
         case "string":
           input.type = "text";
           input.placeholder = def.placeholder;
-        break;
+          break;
       }
 
       // use photoshop-style label-dragging
       // and show filters' unit next to their <input>
       if (def.type !== "string") {
         unitPreview.textContent = filter.unit;
 
         label.classList.add("devtools-draglabel");
@@ -632,17 +635,18 @@ CSSFilterEditorWidget.prototype = {
       } else {
         // string-type filters have no unit
         unitPreview.remove();
       }
 
       this.filtersList.appendChild(el);
     }
 
-    let lastInput = this.filtersList.querySelector(`.filter:last-of-type input`);
+    let lastInput =
+        this.filtersList.querySelector(`.filter:last-of-type input`);
     if (lastInput) {
       lastInput.focus();
       // move cursor to end of input
       const end = lastInput.value.length;
       lastInput.setSelectionRange(end, end);
     }
 
     this.emit("render");
@@ -680,16 +684,17 @@ CSSFilterEditorWidget.prototype = {
     * returns definition of a filter as defined in filterList
     *
     * @param {String} name
     *        filter name (e.g. blur)
     * @return {Object}
     *        filter's definition
     */
   _definition: function(name) {
+    name = name.toLowerCase();
     return filterList.find(a => a.name === name);
   },
 
   /**
     * Parses the CSS value specified, updating widget's filters
     *
     * @param {String} cssValue
     *        css value to be parsed
@@ -703,39 +708,63 @@ CSSFilterEditorWidget.prototype = {
 
     if (cssValue === "none") {
       this.emit("updated", this.getCssValue());
       this.render();
       return;
     }
 
     for (let {name, value} of tokenizeFilterValue(cssValue)) {
+      // If the specified value is invalid, replace it with the
+      // default.
+      if (name !== "url") {
+        if (!DOMUtils.cssPropertyIsValid("filter", name + "(" + value + ")")) {
+          value = null;
+        }
+      }
+
       this.add(name, value);
     }
 
     this.emit("updated", this.getCssValue());
     this.render();
   },
 
   /**
     * Creates a new [name] filter record with value
     *
     * @param {String} name
     *        filter name (e.g. blur)
     * @param {String} value
     *        value of the filter (e.g. 30px, 20%)
+    *        If this is |null|, then a default value may be supplied.
     * @return {Number}
     *        The index of the new filter in the current list of filters
     */
-  add: function(name, value = "") {
+  add: function(name, value) {
     const def = this._definition(name);
     if (!def) {
       return false;
     }
 
+    if (value === null) {
+      // UNIT_MAPPING[string] is an empty string (falsy), so
+      // using || doesn't work here
+      const unitLabel = typeof UNIT_MAPPING[def.type] === "undefined" ?
+                               UNIT_MAPPING[DEFAULT_FILTER_TYPE] :
+                               UNIT_MAPPING[def.type];
+
+      // string-type filters have no default value but a placeholder instead
+      if (!unitLabel) {
+        value = "";
+      } else {
+        value = def.range[0] + unitLabel;
+      }
+    }
+
     let unit = def.type === "string"
                ? ""
                : (/[a-zA-Z%]+/.exec(value) || [])[0];
 
     if (def.type !== "string") {
       value = parseFloat(value);
 
       // You can omit percentage values' and use a value between 0..1
@@ -747,17 +776,17 @@ CSSFilterEditorWidget.prototype = {
       const [min, max] = def.range;
       if (value < min) {
         value = min;
       } else if (value > max) {
         value = max;
       }
     }
 
-    const index = this.filters.push({value, unit, name: def.name}) - 1;
+    const index = this.filters.push({value, unit, name}) - 1;
     this.emit("updated", this.getCssValue());
 
     return index;
   },
 
   /**
     * returns value + unit of the specified filter
     *
@@ -935,18 +964,18 @@ function tokenizeFilterValue(css) {
   *         start: The number's starting index
   *         end: The number's ending index
   */
 function getNeighbourNumber(string, index) {
   if (!/\d/.test(string)) {
     return null;
   }
 
-  let left = /-?[0-9.]*$/.exec(string.slice(0, index)),
-      right = /-?[0-9.]*/.exec(string.slice(index));
+  let left = /-?[0-9.]*$/.exec(string.slice(0, index));
+  let right = /-?[0-9.]*/.exec(string.slice(index));
 
   left = left ? left[0] : "";
   right = right ? right[0] : "";
 
   if (!right && !left) {
     return null;
   }
 
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -14,19 +14,34 @@
 .theme-light {
   --cell-border-color: rgba(0,0,0,0.15);
   --cell-border-color-light: rgba(0,0,0,0.1);
   --focus-cell-border-color: rgba(0,0,0,0.3);
   --row-alt-background-color: rgba(76,158,217,0.1);
   --row-hover-background-color: rgba(76,158,217,0.2);
 }
 
+html, .theme-body, #app, #memory-tool, #memory-tool-container {
+  height: 100%;
+}
+
+.theme-body {
+  overflow: hidden;
+  background-color: var(--theme-body-background);
+}
+
 #memory-tool-container {
   display: flex;
   flex-direction: row;
+  --toolbar-height: 20px;
+  /**
+   * If --heap-tree-row-height changes, be sure to change HEAP_TREE_ROW_HEIGHT
+   * in `devtools/client/memory/components/heap.js`.
+   */
+  --heap-tree-row-height: 14px;
 }
 
 /**
  * TODO bug 1213100
  * should generalize toolbar buttons with images in them
  * toolbars.inc.css contains definitions for .devtools-button,
  * I wager that many of the below styles can be rolled into that
  */
@@ -58,17 +73,17 @@
 /**
  * TODO bug 1213100
  * Should this be codified in .devtools-toolbar itself?
  */
 #memory-tool .devtools-toolbar {
   display: flex;
   flex-direction: row;
   align-items: center;
-  height: 20px;
+  height: var(--toolbar-height);
 }
 
 /**
  * TODO bug 1213100
  * Once we figure out how to store invertable buttons (pseudo element like in this case?)
  * we should add a .invertable class to handle this generally, rather than the definitions
  * in toolbars.inc.css.
  *
@@ -86,23 +101,28 @@
  */
 
 .list {
   margin: 0;
   padding: 0;
   width: 186px;
   list-style-type: none;
   font-size: 12px;
+  height: 100%;
+  overflow-y: scroll;
+  background-color: var(--theme-toolbar-background);
+  border-color: var(--theme-splitter-color);
+  color: var(--theme-body-color-alt);
+  border-left-width: 1px;
 }
 
 .list > li {
   height: 40px;
   color: var(--theme-body-color);
-  border-bottom: 1px solid transparent;
-  border-top: 1px solid rgba(128,128,128,0.15);
+  border-bottom: 1px solid rgba(128,128,128,0.15);
   padding: 8px;
   cursor: pointer;
 }
 
 .list > li.selected {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
@@ -122,81 +142,105 @@
 }
 
 /**
  * Main panel
  */
 
 #heap-view {
   flex: 1 1 auto;
+  border-color: var(--theme-splitter-color);
+  color: var(--theme-body-color)
+  border-left-width: 1px;
 }
 
 #heap-view .heap-view-panel {
   width: 100%;
   height: 100%;
 }
 
 #heap-view .take-snapshot {
 }
 
 /**
- * Heap View
+ * Heap Tree View
  */
 
-.heap-view {
-  position: relative;
+#heap-view .theme-twisty {
+  float: left;
 }
 
-.heap-view .theme-twisty {
-  text-align: end;
+.tree {
+  height: 100%;
+  overflow-y: scroll;
+}
+
+.tree .header {
+  height: 17px;
+  overflow: hidden;
+  color: var(--theme-body-color);
+  background-color: var(--theme-tab-toolbar-background);
 }
 
-.heap-tree-item {
-  list-style-type: none;
-  /* display: none; */
+.tree span {
+  border-left-color: var(--cell-border-color);
+  border-left-width: 1px;
+}
+
+.tree-node {
+  height: var(--heap-tree-row-height);
 }
 
-.heap-tree-item[expanded] {
-  display: block;
+.heap-tree-item, .header {
+  list-style-type: none;
+  height: var(--heap-tree-row-height);
 }
 
-.heap-tree-item:nth-child(2n) {
+.heap-tree-item span, .header span {
+  float: left;
+}
+
+.tree-node:nth-child(2n) {
   background-color: var(--row-alt-background-color);
 }
 
-.heap-tree-item:hover {
+.tree-node:hover {
   background-color: var(--row-hover-background-color);
 }
 
-.heap-tree-item:focus {
+.tree-node:focus {
   background-color: var(--theme-selection-background);
 }
 
-.heap-tree-item:focus description {
-  color: var(--theme-selection-color) !important;
-}
-
-.heap-tree-item:focus .call-tree-cell {
-  -moz-border-end-color: var(--focus-cell-border-color);
+.header {
+  background-color: var(--theme-tab-toolbar-background);
 }
 
-
-.heap-tree-cell[type="bytes"], .heap-tree-cell[type="count"] {
-  position: absolute;
-  text-align: right;
-  width: 40px;
-}
-
-.heap-tree-cell[type="name"] {
-  width: 150px;
+.header span {
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
-  display: block;
+  text-align: center;
+  font-size: 90%;
+}
+
+.heap-tree-item-bytes,
+.heap-tree-item-count,
+.heap-tree-item-total-bytes,
+.heap-tree-item-total-count {
+  text-align: right;
+  border-right: var(--cell-border-color) 1px solid;
+  padding-right: 5px;
 }
 
-.heap-tree-cell[type="count"] {
-  left: 300px;
+.heap-tree-item-count,
+.heap-tree-item-total-count {
+  width: 5vw;
 }
 
-.heap-tree-cell[type="bytes"] {
-  left: 250px;
+.heap-tree-item-bytes,
+.heap-tree-item-total-bytes {
+  width: 7vw;
 }
+
+.heap-tree-item-name {
+  padding-left: 10px;
+}
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -544,16 +544,25 @@
   padding: 2px 5px;
   border-width: 1px;
 }
 
 /**
  * Marker colors
  */
 
+menuitem.marker-color-graphs-full-red:before,
+.marker-color-graphs-full-red {
+  background-color: var(--theme-graphs-full-red);
+}
+menuitem.marker-color-graphs-full-blue:before,
+.marker-color-graphs-full-blue {
+  background-color: var(--theme-graphs-full-blue);
+}
+
 menuitem.marker-color-graphs-green:before,
 .marker-color-graphs-green {
   background-color: var(--theme-graphs-green);
 }
 menuitem.marker-color-graphs-blue:before,
 .marker-color-graphs-blue {
   background-color: var(--theme-graphs-blue);
 }
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -46,16 +46,18 @@
   --theme-graphs-green: #85d175;
   --theme-graphs-blue: #83b7f6;
   --theme-graphs-bluegrey: #0072ab;
   --theme-graphs-purple: #b693eb;
   --theme-graphs-yellow: #efc052;
   --theme-graphs-orange: #d97e00;
   --theme-graphs-red: #e57180;
   --theme-graphs-grey: #cccccc;
+  --theme-graphs-full-red: #f00;
+  --theme-graphs-full-blue: #00f;
 }
 
 :root.theme-dark {
   --theme-body-background: #14171a;
   --theme-sidebar-background: #181d20;
   --theme-contrast-background: #b28025;
 
   --theme-tab-toolbar-background: #252c33;
@@ -85,9 +87,11 @@
   --theme-graphs-green: #70bf53;
   --theme-graphs-blue: #46afe3;
   --theme-graphs-bluegrey: #5e88b0;
   --theme-graphs-purple: #df80ff;
   --theme-graphs-yellow: #d99b28;
   --theme-graphs-orange: #d96629;
   --theme-graphs-red: #eb5368;
   --theme-graphs-grey: #757873;
+  --theme-graphs-full-red: #f00;
+  --theme-graphs-full-blue: #00f;
 }
--- a/devtools/shared/output-parser.js
+++ b/devtools/shared/output-parser.js
@@ -72,17 +72,20 @@ OutputParser.prototype = {
 
     options.expectCubicBezier =
       safeCssPropertySupportsType(name, DOMUtils.TYPE_TIMING_FUNCTION);
     options.expectFilter = name === "filter";
     options.supportsColor =
       safeCssPropertySupportsType(name, DOMUtils.TYPE_COLOR) ||
       safeCssPropertySupportsType(name, DOMUtils.TYPE_GRADIENT);
 
-    if (this._cssPropertySupportsValue(name, value)) {
+    // The filter property is special in that we want to show the
+    // swatch even if the value is invalid, because this way the user
+    // can easily use the editor to fix it.
+    if (options.expectFilter || this._cssPropertySupportsValue(name, value)) {
       return this._parse(value, options);
     }
     this._appendTextNode(value);
 
     return this._toDOM();
   },
 
   /**
--- a/dom/apps/AppsUtils.jsm
+++ b/dom/apps/AppsUtils.jsm
@@ -125,16 +125,17 @@ function _setAppProperties(aObj, aApp) {
   aObj.storeId = aApp.storeId || "";
   aObj.storeVersion = aApp.storeVersion || 0;
   aObj.role = aApp.role || "";
   aObj.redirects = aApp.redirects;
   aObj.widgetPages = aApp.widgetPages || [];
   aObj.kind = aApp.kind;
   aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
   aObj.sideloaded = aApp.sideloaded;
+  aObj.extensionVersion = aApp.extensionVersion;
 #ifdef MOZ_B2GDROID
   aObj.android_packagename = aApp.android_packagename;
   aObj.android_classname = aApp.android_classname;
 #endif
 }
 
 this.AppsUtils = {
   // Clones a app, without the manifest.
--- a/dom/apps/UserCustomizations.jsm
+++ b/dom/apps/UserCustomizations.jsm
@@ -133,16 +133,20 @@ this.UserCustomizations = {
     if (aManifest.description) {
       result.description = aManifest.description;
     }
 
     if (aManifest.icons) {
       result.icons = aManifest.icons;
     }
 
+    if (aManifest.version) {
+      result.version = aManifest.version;
+    }
+
     // chrome extension manifests have a single 'author' property, that we
     // map to 'developer.name'.
     // Note that it has to match the one in the mini-manifest.
     if (aManifest.author) {
       result.developer = {
         name: aManifest.author
       }
     }
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -3807,16 +3807,20 @@ this.DOMApplicationRegistry = {
           NetUtil.readInputStreamToString(istream, istream.available()) || ""));
 
     if (!hasWebappManifest) {
       // Validate the extension manifest, and convert it.
       if (!UserCustomizations.checkExtensionManifest(newManifest)) {
         throw "INVALID_MANIFEST";
       }
       newManifest = UserCustomizations.convertManifest(newManifest);
+      // Keep track of the add-on version, to use for blocklisting.
+      if (newManifest.version) {
+        aNewApp.extensionVersion = newManifest.version;
+      }
     }
 
     if (!AppsUtils.checkManifest(newManifest, aOldApp)) {
       throw "INVALID_MANIFEST";
     }
 
     // For app updates we don't forbid apps to rename themselves but
     // we still retain the old name of the app. In the future we
--- a/dom/apps/tests/test_app_addons.html
+++ b/dom/apps/tests/test_app_addons.html
@@ -74,20 +74,22 @@ let apps = [];
 
 function installApp(manifestURL, expectedError) {
   info("About to install app at " + manifestURL);
   let req = navigator.mozApps.installPackage(manifestURL);
   req.onsuccess = function() {
     apps.push(req.result);
     is(req.result.manifestURL, manifestURL, "app installed");
     if (req.result.installState == "installed") {
+      is(req.result.manifest.version, "1.0", "correct version");
       is(req.result.installState, "installed", "app downloaded");
       continueTest();
     } else {
       req.result.ondownloadapplied = function() {
+        is(req.result.manifest.version, "1.0", "correct version");
         is(req.result.installState, "installed", "app downloaded");
         continueTest();
       }
 
       req.result.ondownloaderror = function() {
         if (expectedError) {
           is(req.result.downloadError.name, "MANIFEST_MISMATCH");
         } else {
--- a/dom/apps/tests/test_third_party_homescreen.html
+++ b/dom/apps/tests/test_third_party_homescreen.html
@@ -159,21 +159,21 @@ function runTest() {
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
 
   var app = request.result;
   ok(app, "App is non-null");
   is(app.manifestURL, gManifestURL, "App manifest url is correct.");
 
-  var context = {"manifestURL": app.manifestURL, "isInBrowserElement": false};
+  var context = {manifestURL: app.manifestURL};
 
-  SpecialPowers.pushPermissions([{"type": "homescreen-webapps-manage",
-                                  "allow": 1,
-                                  "context": context}], continueTest);
+  SpecialPowers.pushPermissions([{type: "homescreen-webapps-manage",
+                                  allow: 1,
+                                  context: context}], continueTest);
   yield undefined;
 
   // Launch the app.
   info("Running " + app.manifestURL);
   runApp(app, continueTest);
   yield undefined;
 
   // Uninstall the app to cleanup after ourself.
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1602,31 +1602,33 @@ already_AddRefed<Promise>
 Navigator::HasFeature(const nsAString& aName, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  // Hardcoded web-extensions feature which is b2g specific.
+  // Hardcoded extensions features which are b2g specific.
 #ifdef MOZ_B2G
-  if (aName.EqualsLiteral("web-extensions")) {
+  if (aName.EqualsLiteral("web-extensions") ||
+      aName.EqualsLiteral("late-customization")) {
     p->MaybeResolve(true);
     return p.forget();
   }
 #endif
 
   // Hardcoded manifest features. Some are still b2g specific.
   const char manifestFeatures[][64] = {
     "manifest.origin"
   , "manifest.redirects"
 #ifdef MOZ_B2G
   , "manifest.chrome.navigation"
   , "manifest.precompile"
+  , "manifest.role.homescreen"
 #endif
   };
 
   nsAutoCString feature = NS_ConvertUTF16toUTF8(aName);
   for (uint32_t i = 0; i < MOZ_ARRAY_LENGTH(manifestFeatures); i++) {
     if (feature.Equals(manifestFeatures[i])) {
       p->MaybeResolve(true);
       return p.forget();
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -5104,16 +5104,24 @@ nsDocument::DispatchContentLoadedEvents(
 
   // Fire a DOM event notifying listeners that this document has been
   // loaded (excluding images and other loads initiated by this
   // document).
   nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
                                        NS_LITERAL_STRING("DOMContentLoaded"),
                                        true, false);
 
+  RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+  nsIDocShell* docShell = this->GetDocShell();
+
+  if (timelines && timelines->HasConsumer(docShell)) {
+    timelines->AddMarkerForDocShell(
+      docShell, "document::DOMContentLoaded", MarkerTracingType::TIMESTAMP);
+  }
+
   if (mTiming) {
     mTiming->NotifyDOMContentLoadedEnd(nsIDocument::GetDocumentURI());
   }
 
   // If this document is a [i]frame, fire a DOMFrameContentLoaded
   // event on all parent documents notifying that the HTML (excluding
   // other external files such as images and stylesheets) in a frame
   // has finished loading.
--- a/dom/base/test/test_getFeature_with_perm.html
+++ b/dom/base/test/test_getFeature_with_perm.html
@@ -121,16 +121,17 @@ SpecialPowers.pushPermissions([
   ok('hasFeature' in navigator, "navigator.hasFeature should exist");
   // B2G specific manifest features.
   // Touching navigator before pushPermissions makes it fail.
   if (!navigator.userAgent.includes("Android") &&
         /Mobile|Tablet/.test(navigator.userAgent)) {
     info("Adding B2G specific tests");
     tests.push(createManifestTest("manifest.chrome.navigation"));
     tests.push(createManifestTest("manifest.precompile"));
+    tests.push(createManifestTest("manifest.role.homescreen"));
   }
   runNextTest();
   ok(true, "Test DONE");
 });
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
--- a/dom/base/test/test_hasFeature.html
+++ b/dom/base/test/test_hasFeature.html
@@ -60,21 +60,29 @@ function testAPIs() {
   });
 }
 
 function testExtensions() {
   if (!b2gOnly) {
     return Promise.resolve();
   }
 
-  return navigator.hasFeature("web-extensions").then(function(value) {
-    is(value, true, "Resolve the Promise with " + value + " for web-extensions");
-  }, function() {
-    ok(false, "The Promise should not be rejected");
+  var builtInFeatures = [
+    {feature: "web-extensions", value: true},
+    {feature: "late-customization", value: true}
+  ];
+
+  builtInFeatures.forEach(function(x) {
+    navigator.hasFeature(x.feature).then(function(value) {
+      is(value, x.value, "Resolve the Promise with " + value + " for feature: " + x.feature);
+    }).catch(function(ex) {
+      ok(false, "The Promise should not be rejected");
+    });
   });
+  return Promise.resolve();
 }
 
 SpecialPowers.pushPermissions([
   {type: "feature-detection", allow: true, context: document}
 ], function() {
   b2gOnly = (function() {
     var isAndroid = !!navigator.userAgent.includes("Android");
     var isMulet = pref("b2g.is_mulet");
--- a/dom/base/test/test_messagemanager_assertpermission.html
+++ b/dom/base/test/test_messagemanager_assertpermission.html
@@ -27,18 +27,18 @@ let gAppsService = SpecialPowers.Cc["@mo
                      .getService(SpecialPowers.Ci.nsIAppsService);
 
 function setUp() {
   SpecialPowers.addPermission("browser", true, window.document);
   SpecialPowers.addPermission("embed-apps", true, window.document);
 
   let appId = gAppsService.getAppLocalIdByManifestURL(APP_MANIFEST);
   SpecialPowers.addPermission("foobar", true, { url: APP_URL,
-                                                appId: appId,
-                                                isInBrowserElement: false });
+                                                originAttributes: { appId: appId }
+                                              });
   SpecialPowers.pushPrefEnv({"set":[['dom.mozBrowserFramesEnabled', true],
                                     ['dom.ipc.browser_frames.oop_by_default', true]]}, runNextTest);
 }
 
 /**
  * Load the example.org app in an <iframe mozbrowser mozapp>
  */
 function loadApp(callback) {
--- a/dom/base/test/test_messagemanager_targetchain.html
+++ b/dom/base/test/test_messagemanager_targetchain.html
@@ -98,25 +98,27 @@
         ok(false, "top document shouldn't receive test event from child");
       }, true);
       document.body.appendChild(iframe);
     }
 
     addEventListener("load", function() {
       var principal = SpecialPowers.wrap(document).nodePrincipal;
       SpecialPowers.pushPermissions([
-        { "type": "browser", "allow": 1, "context": { "url": principal.URI.spec,
-                                                      "appId": principal.appId,
-                                                      "isInBrowserElement": false }},
-        { "type": "browser", "allow": 1, "context": { "url": principal.URI.spec,
-                                                      "appId": principal.appId,
-                                                      "isInBrowserElement": true }}
+        { type: "browser", allow: 1, context: { url: principal.URI.spec,
+                                                originAttributes: {
+                                                  appId: principal.appId
+                                                }}},
+        { type: "browser", allow: 1, context: { url: principal.URI.spec,
+                                                originAttributes: {
+                                                  appId: principal.appId,
+                                                  inBrowser: true }}}
       ], () => {
         SpecialPowers.pushPrefEnv({
-          "set": [
+          set: [
             ["dom.mozBrowserFramesEnabled", true],
             ["dom.ipc.browser_frames.oop_by_default", false],
           ]
         }, runTests);
       });
     });
   </script>
 </body>
--- a/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp
@@ -472,16 +472,21 @@ BluetoothPbapManager::NotifyPbapRequest(
     // ... PullvCardListing function uses relative paths. An empty name header
     // may be sent to retrieve the vCard Listing object of the current folder.
     name = name.IsEmpty() ? mCurrentPath
                           : mCurrentPath + NS_LITERAL_STRING("/") + name;
   } else if (type.EqualsLiteral("x-bt/vcard")) {
     reqId.AssignLiteral(PULL_VCARD_ENTRY_REQ_ID);
     tagCount = MOZ_ARRAY_LENGTH(sVCardEntryTags);
     tags = sVCardEntryTags;
+
+    // Convert relative path to absolute path if it's not using X-BT-UID.
+    if (name.Find(NS_LITERAL_STRING("X-BT-UID")) == kNotFound) {
+      name = mCurrentPath + NS_LITERAL_STRING("/") + name;
+    }
   } else {
     BT_LOGR("Unknown PBAP request type: %s",
             NS_ConvertUTF16toUTF8(type).get());
     return ObexResponseCode::BadRequest;
   }
 
   // Ensure bluetooth service is available
   BluetoothService* bs = BluetoothService::Get();
--- a/dom/browser-element/mochitest/browserElement_AllowEmbedAppsInNestedOOIframe.js
+++ b/dom/browser-element/mochitest/browserElement_AllowEmbedAppsInNestedOOIframe.js
@@ -17,22 +17,21 @@ function runTest() {
   iframe.setAttribute('mozbrowser', 'true');
   iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
     is(e.detail.message == 'app', true, e.detail.message);
     SimpleTest.finish();
   });
 
   document.body.appendChild(iframe);
 
-  var context = { 'url': 'http://example.org',
-                  'appId': SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-                  'isInBrowserElement': true };
+  var context = {url: 'http://example.org',
+                 originAttributes: {inBrowser: true}};
   SpecialPowers.pushPermissions([
-    {'type': 'browser', 'allow': 1, 'context': context},
-    {'type': 'embed-apps', 'allow': 1, 'context': context}
+    {type: 'browser', allow: 1, context: context},
+    {type: 'embed-apps', allow: 1, context: context}
   ], function() {
     iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_AllowEmbedAppsInNestedOOIframe.html';
   });
 }
 
 addEventListener('testready', () => {
-  SpecialPowers.pushPrefEnv({"set": [["dom.ipc.tabs.nested.enabled", true]]}, runTest);
+  SpecialPowers.pushPrefEnv({set: [["dom.ipc.tabs.nested.enabled", true]]}, runTest);
 });
--- a/dom/browser-element/mochitest/browserElement_CopyPaste.js
+++ b/dom/browser-element/mochitest/browserElement_CopyPaste.js
@@ -338,18 +338,19 @@ function testCut2(e) {
     dispatchTest(e);
   });
 
   mm.loadFrameScript(getScriptForGetContent(), false);
 }
 
 // Give our origin permission to open browsers, and remove it when the test is complete.
 var principal = SpecialPowers.wrap(document).nodePrincipal;
-var context = { 'url': SpecialPowers.wrap(principal.URI).spec,
-                'appId': principal.appId,
-                'isInBrowserElement': true };
+var context = { url: SpecialPowers.wrap(principal.URI).spec,
+                originAttributes: {
+                  appId: principal.appId,
+                  inBrowser: true }};
 
 addEventListener('testready', function() {
   SpecialPowers.pushPermissions([
-    {'type': 'browser', 'allow': 1, 'context': context}
+    {type: 'browser', allow: 1, context: context}
   ], runTest);
 });
 
--- a/dom/browser-element/mochitest/browserElement_DisallowEmbedAppsInOOP.js
+++ b/dom/browser-element/mochitest/browserElement_DisallowEmbedAppsInOOP.js
@@ -23,20 +23,19 @@ function runTest() {
 
   iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
     is(e.detail.message == 'app', canEmbedApp, e.detail.message);
     SimpleTest.finish();
   });
 
   document.body.appendChild(iframe);
 
-  var context = { 'url': 'http://example.org',
-                  'appId': SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-                  'isInBrowserElement': true };
+  var context = {url: 'http://example.org',
+                 originAttributes: {inBrowser: true}};
   SpecialPowers.pushPermissions([
-    {'type': 'browser', 'allow': 1, 'context': context},
-    {'type': 'embed-apps', 'allow': 1, 'context': context}
+    {type: 'browser', allow: 1, context: context},
+    {type: 'embed-apps', allow: 1, context: context}
   ], function() {
     iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_DisallowEmbedAppsInOOP.html';
   });
 }
 
 addEventListener('testready', runTest);
--- a/dom/browser-element/mochitest/browserElement_Proxy.js
+++ b/dom/browser-element/mochitest/browserElement_Proxy.js
@@ -9,26 +9,28 @@ browserElementTestHelpers.addPermission(
 
 function runTest() {
   let frameUrl = SimpleTest.getTestFileURL('/file_empty.html');
   SpecialPowers.pushPermissions([{
     type: 'browser:embedded-system-app',
     allow: true,
     context: {
       url: frameUrl,
-      appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-      isInBrowserElement: true
+      originAttributes: {
+        inBrowser: true
+      }
     }
   },{
     type: 'browser',
     allow: true,
     context: {
       url: frameUrl,
-      appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-      isInBrowserElement: true
+      originAttributes: {
+        inBrowser: true
+      }
     }
   }], createFrame);
 }
 
 var frame;
 var mm;
 
 function createFrame() {
--- a/dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
+++ b/dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
@@ -31,18 +31,17 @@ function runTest() {
   let imeUrl = location.protocol + '//' + location.host +
                path.substring(0, path.lastIndexOf('/')) +
                '/file_inputmethod.html';
   SpecialPowers.pushPermissions([{
     type: 'input',
     allow: true,
     context: {
       url: imeUrl,
-      appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-      isInBrowserElement: true
+      originAttributes: {inBrowser: true}
     }
   }], SimpleTest.waitForFocus.bind(SimpleTest, createFrames));
 }
 
 var gFrames = [];
 var gInputFrame;
 
 function createFrames() {
--- a/dom/browser-element/mochitest/browserElement_SetVisibleFrames.js
+++ b/dom/browser-element/mochitest/browserElement_SetVisibleFrames.js
@@ -13,19 +13,21 @@
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
 var iframe;
 
 function runTest() {
   var principal = SpecialPowers.wrap(document).nodePrincipal;
-  SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec,
-                                                 appId: principal.appId,
-                                                 isInBrowserElement: true });
+  SpecialPowers.addPermission("browser", true, {url: SpecialPowers.wrap(principal.URI).spec,
+                                                originAttributes: {
+                                                  appId: principal.appId,
+                                                  inBrowser: true
+                                                }});
 
   iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
 
   // Our test involves three <iframe mozbrowser>'s, parent, child1, and child2.
   // child1 and child2 are contained inside parent.  child1 is visibile, and
   // child2 is not.
   //
@@ -70,20 +72,21 @@ function finish() {
   // We need to remove this listener because when this test finishes and the
   // iframe containing this document is navigated, we'll fire a
   // visibilitychange(false) event on all child iframes.  That's OK and
   // expected, but if we don't remove our listener, then we'll end up causing
   // the /next/ test to fail!
   iframe.removeEventListener('mozbrowsershowmodalprompt', checkMessage);
 
   var principal = SpecialPowers.wrap(document).nodePrincipal;
-  SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
-                                              appId: principal.appId,
-                                              isInBrowserElement: true });
-
+  SpecialPowers.removePermission("browser", {url: SpecialPowers.wrap(principal.URI).spec,
+                                             originAttributes: {
+                                               appId: principal.appId,
+                                               inBrowser: true
+                                             }});
   SimpleTest.finish();
 }
 
 var expectedMsg = null;
 var expectedMsgCallback = null;
 function expectMessage(msg, next) {
   expectedMsg = msg;
   expectedMsgCallback = next;
--- a/dom/browser-element/mochitest/browserElement_SetVisibleFrames2.js
+++ b/dom/browser-element/mochitest/browserElement_SetVisibleFrames2.js
@@ -7,19 +7,21 @@
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
 function runTest() {
   var principal = SpecialPowers.wrap(document).nodePrincipal;
-  SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec,
-                                                 appId: principal.appId,
-                                                 isInBrowserElement: true });
+  SpecialPowers.addPermission("browser", true, {url: SpecialPowers.wrap(principal.URI).spec,
+                                                originAttributes: {
+                                                  appId: principal.appId,
+                                                  inBrowser: true
+                                                }});
 
   var iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
 
   // We need remote = false here until bug 761935 is fixed; see
   // SetVisibleFrames.js for an explanation.
   iframe.remote = false;
 
@@ -51,16 +53,18 @@ function runTest() {
     }
   });
 
   document.body.appendChild(iframe);
 }
 
 function finish() {
   var principal = SpecialPowers.wrap(document).nodePrincipal;
-  SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
-                                              appId: principal.appId,
-                                              isInBrowserElement: true });
+  SpecialPowers.removePermission("browser", {url: SpecialPowers.wrap(principal.URI).spec,
+                                             originAttributes: {
+                                               appId: principal.appId,
+                                               inBrowser: true
+                                             }});
 
   SimpleTest.finish();
 }
 
 addEventListener('testready', runTest);
--- a/dom/browser-element/mochitest/priority/test_ExpectingSystemMessage2.html
+++ b/dom/browser-element/mochitest/priority/test_ExpectingSystemMessage2.html
@@ -17,25 +17,29 @@ message gets priority BACKGROUND_PERCEIV
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 browserElementTestHelpers.enableProcessPriorityManager();
 SpecialPowers.addPermission("embed-apps", true, document);
 
 // Give our origin permission to open browsers, and remove it when the test is complete.
 var principal = SpecialPowers.wrap(document).nodePrincipal;
-SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec,
-                                               appId: principal.appId,
-                                               isInBrowserElement: true });
+SpecialPowers.addPermission("browser", true, {url: SpecialPowers.wrap(principal.URI).spec,
+                                              originAttributes: {
+                                                appId: principal.appId,
+                                                inBrowser: true
+                                              }});
 
 addEventListener('unload', function() {
   var principal = SpecialPowers.wrap(document).nodePrincipal;
-  SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
-                                              appId: principal.appId,
-                                              isInBrowserElement: true });
+  SpecialPowers.removePermission("browser", {url: SpecialPowers.wrap(principal.URI).spec,
+                                             originAttributes: {
+                                               appId: principal.appId,
+                                               inBrowser: true
+                                             }});
 });
 
 function runTest() {
   var iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', true);
   iframe.setAttribute('expecting-system-message', true);
   iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
 
--- a/dom/browser-element/mochitest/priority/test_NestedFrames.html
+++ b/dom/browser-element/mochitest/priority/test_NestedFrames.html
@@ -16,25 +16,29 @@ Test changing the visibility of an <ifra
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 browserElementTestHelpers.enableProcessPriorityManager();
 
 // Give our origin permission to open browsers, and remove it when the test is complete.
 var principal = SpecialPowers.wrap(document).nodePrincipal;
-SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec,
-                                               appId: principal.appId,
-                                               isInBrowserElement: true });
+SpecialPowers.addPermission("browser", true, {url: SpecialPowers.wrap(principal.URI).spec,
+                                              originAttributes: {
+                                                appId: principal.appId,
+                                                inBrowser: true
+                                              }});
 
 addEventListener('unload', function() {
   var principal = SpecialPowers.wrap(document).nodePrincipal;
-  SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
-                                              appId: principal.appId,
-                                              isInBrowserElement: true });
+  SpecialPowers.removePermission("browser", {url: SpecialPowers.wrap(principal.URI).spec,
+                                             originAttributes: {
+                                               appId: principal.appId,
+                                               inBrowser: true
+                                             }});
 });
 
 function runTest() {
   // Set up the following hierarchy of frames:
   //
   //   <iframe mozbrowser remote=false src='file_NestedFramesOuter.html'>
   //     <iframe mozbrowser remote=true src='file_empty.html'>
   //
--- a/dom/cache/test/mochitest/driver.js
+++ b/dom/cache/test/mochitest/driver.js
@@ -30,26 +30,17 @@ function runTests(testFile, order) {
         resolve();
       });
     });
   }
 
   // adapted from dom/indexedDB/test/helpers.js
   function clearStorage() {
     return new Promise(function(resolve, reject) {
-      var principal = SpecialPowers.wrap(document).nodePrincipal;
-      var appId, inBrowser;
-      var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-      if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-          principal.appId != nsIPrincipal.NO_APP_ID) {
-        appId = principal.appId;
-        inBrowser = principal.isInBrowserElement;
-      }
-      SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
-                                       inBrowser);
+      SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
     });
   }
 
   function loadScript(script) {
     return new Promise(function(resolve, reject) {
       var s = document.createElement("script");
       s.src = script;
       s.onerror = reject;
--- a/dom/cache/test/mochitest/test_cache_orphaned_body.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html
@@ -19,56 +19,29 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function clearStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function storageUsage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.getStorageUsageForURI(document.documentURI, resolve, appId,
-                                        inBrowser);
+    SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.resetStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
     SpecialPowers.exactGC(window, resolve);
   });
 }
--- a/dom/cache/test/mochitest/test_cache_orphaned_cache.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html
@@ -19,56 +19,29 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function clearStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function storageUsage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.getStorageUsageForURI(document.documentURI, resolve, appId,
-                                        inBrowser);
+    SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.resetStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
     SpecialPowers.exactGC(window, resolve);
   });
 }
--- a/dom/cache/test/mochitest/test_cache_restart.html
+++ b/dom/cache/test/mochitest/test_cache_restart.html
@@ -18,26 +18,17 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.resetStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/cache/test/mochitest/test_cache_shrink.html
+++ b/dom/cache/test/mochitest/test_cache_shrink.html
@@ -19,56 +19,29 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function clearStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function storageUsage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.getStorageUsageForURI(document.documentURI, resolve, appId,
-                                        inBrowser);
+    SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    var principal = SpecialPowers.wrap(document).nodePrincipal;
-    var appId, inBrowser;
-    var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
-    if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
-        principal.appId != nsIPrincipal.NO_APP_ID) {
-      appId = principal.appId;
-      inBrowser = principal.isInBrowserElement;
-    }
-    SpecialPowers.resetStorageForURI(document.documentURI, resolve, appId,
-                                     inBrowser);
+    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
     SpecialPowers.exactGC(window, resolve);
   });
 }
--- a/dom/indexedDB/test/file.js
+++ b/dom/indexedDB/test/file.js
@@ -184,27 +184,17 @@ function verifyMutableFile(mutableFile1,
 
 function grabFileUsageAndContinueHandler(usage, fileUsage)
 {
   testGenerator.send(fileUsage);
 }
 
 function getUsage(usageHandler)
 {
-  let principal = SpecialPowers.wrap(document).nodePrincipal;
-  let appId, inBrowser;
-  if (principal.appId != Components.interfaces.nsIPrincipal.UNKNOWN_APP_ID &&
-      principal.appId != Components.interfaces.nsIPrincipal.NO_APP_ID) {
-    appId = principal.appId;
-    inBrowser = principal.isInBrowserElement;
-  }
-  SpecialPowers.getStorageUsageForURI(window.document.documentURI,
-                                      usageHandler,
-                                      appId,
-                                      inBrowser);
+  SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), usageHandler);
 }
 
 function getFileId(file)
 {
   return utils.getFileId(file);
 }
 
 function getFilePath(file)
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -29,24 +29,17 @@ function executeSoon(aFun)
   thread.dispatch({
     run: function() {
       aFun();
     }
   }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
 }
 
 function clearAllDatabases(callback) {
-  let principal = SpecialPowers.wrap(document).nodePrincipal;
-  let appId, inBrowser;
-  if (principal.appId != Components.interfaces.nsIPrincipal.UNKNOWN_APP_ID &&
-      principal.appId != Components.interfaces.nsIPrincipal.NO_APP_ID) {
-    appId = principal.appId;
-    inBrowser = principal.isInBrowserElement;
-  }
-  SpecialPowers.clearStorageForURI(document.documentURI, callback, appId, inBrowser);
+  SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), callback);
 }
 
 var testHarnessGenerator = testHarnessSteps();
 testHarnessGenerator.next();
 
 function testHarnessSteps() {
   function nextTestHarnessStep(val) {
     testHarnessGenerator.send(val);
--- a/dom/indexedDB/test/webapp_clearBrowserData.js
+++ b/dom/indexedDB/test/webapp_clearBrowserData.js
@@ -112,21 +112,19 @@ function start()
 {
   if (!SpecialPowers.isMainProcess()) {
     todo(false, "Test disabled in child processes, for now");
     SimpleTest.finish();
     return;
   }
 
   SpecialPowers.addPermission("browser", true, document);
-  SpecialPowers.addPermission("browser", true, { manifestURL: manifestURL,
-                                                 isInBrowserElement: false });
+  SpecialPowers.addPermission("browser", true, { manifestURL: manifestURL });
   SpecialPowers.addPermission("embed-apps", true, document);
-  SpecialPowers.addPermission("indexedDB", true, { manifestURL: manifestURL,
-                                                   isInBrowserElement: false });
+  SpecialPowers.addPermission("indexedDB", true, { manifestURL: manifestURL });
 
   SpecialPowers.setAllAppsLaunchable(true);
 
   window.addEventListener("unload", function cleanup(event) {
     if (event.target == document) {
       window.removeEventListener("unload", cleanup, false);
 
       SpecialPowers.removePermission("browser", location.href);
--- a/dom/inputmethod/mochitest/test_bug1043828.html
+++ b/dom/inputmethod/mochitest/test_bug1043828.html
@@ -86,18 +86,19 @@ function runTest() {
     // simulate two different keyboard apps
     let imeUrl = basePath + '/file_blank.html';
 
     SpecialPowers.pushPermissions([{
       type: 'input',
       allow: true,
       context: {
         url: imeUrl,
-        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-        isInBrowserElement: true
+        originAttributes: {
+          inBrowser: true
+        }
       }
     }], function() {
       keyboardA.src = imeUrl;
       keyboardB.src = imeUrl;
 
       var handler = {
         handleEvent: function(){
           keyboardB.removeEventListener('mozbrowserloadend', this);
--- a/dom/inputmethod/mochitest/test_bug944397.html
+++ b/dom/inputmethod/mochitest/test_bug944397.html
@@ -73,18 +73,19 @@ function runTest() {
     // STEP 2b: Grant input privileges to the keyboard iframe
     let imeUrl = basePath + '/file_inputmethod.html#data';
 
     SpecialPowers.pushPermissions([{
       type: 'input',
       allow: true,
       context: {
         url: imeUrl,
-        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-        isInBrowserElement: true
+        originAttributes: {
+          inBrowser: true
+        }
       }
     }], function() {
       // STEP 2c: Tell Gecko to use this iframe as its keyboard app
       let req = keyboard.setInputMethodActive(true);
 
       req.onsuccess = function() {
         ok(true, 'setInputMethodActive succeeded.');
       };
--- a/dom/inputmethod/mochitest/test_focus_blur_manage_events.html
+++ b/dom/inputmethod/mochitest/test_focus_blur_manage_events.html
@@ -136,18 +136,19 @@ function setupInputAppFrame() {
     inputAppFrame.src = imeUrl;
     document.body.appendChild(inputAppFrame);
 
     SpecialPowers.pushPermissions([{
       type: 'input',
       allow: true,
       context: {
         url: imeUrl,
-        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-        isInBrowserElement: true
+        originAttributes: {
+          inBrowser: true
+        }
       }
     }], function() {
       let mm = SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
       inputAppFrame.addEventListener('mozbrowserloadend', function() {
         mm.addMessageListener('text:appEvent', function(msg) {
           ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
         });
         mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
--- a/dom/inputmethod/mochitest/test_input_registry_events.html
+++ b/dom/inputmethod/mochitest/test_input_registry_events.html
@@ -65,18 +65,19 @@ function setupInputAppFrame() {
     inputAppFrame.src = imeUrl;
     document.body.appendChild(inputAppFrame);
 
     SpecialPowers.pushPermissions([{
       type: 'input',
       allow: true,
       context: {
         url: imeUrl,
-        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-        isInBrowserElement: true
+        originAttributes: {
+          inBrowser: true
+        }
       }
     }], function() {
       let mm = appFrameMM =
         SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
 
       inputAppFrame.addEventListener('mozbrowserloadend', function() {
         mm.addMessageListener('test:appEvent', function(msg) {
           ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
--- a/dom/inputmethod/mochitest/test_simple_manage_events.html
+++ b/dom/inputmethod/mochitest/test_simple_manage_events.html
@@ -98,18 +98,19 @@ function setupInputAppFrame() {
     inputAppFrame.src = imeUrl;
     document.body.appendChild(inputAppFrame);
 
     SpecialPowers.pushPermissions([{
       type: 'input',
       allow: true,
       context: {
         url: imeUrl,
-        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
-        isInBrowserElement: true
+        originAttributes: {
+          inBrowser: true
+        }
       }
     }], function() {
       let mm = appFrameMM =
         SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
 
       inputAppFrame.addEventListener('mozbrowserloadend', function() {
         mm.addMessageListener('test:appEvent', function(msg) {
           ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
--- a/dom/ipc/tests/test_permission_for_nested_oop_app.html
+++ b/dom/ipc/tests/test_permission_for_nested_oop_app.html
@@ -28,18 +28,17 @@ var tests = [
 
   // Install the app
   installApp,
 
   // Allow permission for embedApp to open mozbrowser and mozapp
   function() {
     var appId = gAppsService.getAppLocalIdByManifestURL(embedAppHostedManifestURL);
     var context = { url: embedApp.origin,
-                    appId: appId,
-                    isInBrowserElement: false };
+                    originAttributes: { appId: appId } };
     setupOpenAppPermission(context, runTests);
   },
 
   // +-------------------------------------------+
   // | Test 3: Open a test-target-app on 3rd     |
   // |         level child-process then close it |
   // +-------------------------------------------+
   //
--- a/dom/ipc/tests/test_permission_for_two_oop_apps.html
+++ b/dom/ipc/tests/test_permission_for_two_oop_apps.html
@@ -28,18 +28,17 @@ var tests = [
 
   // Install the app
   installApp,
 
   // Allow permission for embedApp to open mozbrowser and mozapp
   function() {
     var appId = gAppsService.getAppLocalIdByManifestURL(embedAppHostedManifestURL);
     var context = { url: embedApp.origin,
-                    appId: appId,
-                    isInBrowserElement: false };
+                    originAttributes: { appId: appId } };
     setupOpenAppPermission(context, runTests);
   },
 
   // +---------------------------------------------------------+
   // | Test 4: Open two test-target-app on 2nd and 3rd level   |
   // |         child-processes then close the one on 3rd level |
   // +---------------------------------------------------------+
   //
--- a/dom/ipc/tests/test_permission_helper.js
+++ b/dom/ipc/tests/test_permission_helper.js
@@ -48,18 +48,17 @@ function runTests() {
 
 function test1() {
   allocateAppFrame(APP_IFRAME_ID, DOM_PARENT, APP_URL, APP_MANIFEST);
 
   var appId = gAppsService.getAppLocalIdByManifestURL(APP_MANIFEST);
 
   if (!SpecialPowers.hasPermission( PERMISSION_TYPE,
                                     { url: APP_URL,
-                                      appId: appId,
-                                      isInBrowserElement: false })) {
+                                      originAttributes: { appId: appId }})) {
     errorHandler('[test 1] App should have permission: ' + PERMISSION_TYPE);
   }
 
   removeAppFrame(APP_IFRAME_ID);
 
   // We expect there is no permission for the test-target-app
   // after removing the in-process iframe embedding this app
   runNextIfAppHasPermission(1, false, APP_URL, APP_MANIFEST);
@@ -77,18 +76,17 @@ function test2() {
   afterContentShutdown(1, afterShutdown);
 
   allocateAppFrame(APP_IFRAME_ID, DOM_PARENT, APP_URL, APP_MANIFEST, true);
 
   var appId = gAppsService.getAppLocalIdByManifestURL(APP_MANIFEST);
 
   if (!SpecialPowers.hasPermission( PERMISSION_TYPE,
                                     { url: APP_URL,
-                                      appId: appId,
-                                      isInBrowserElement: false })) {
+                                      originAttributes: { appId: appId }})) {
     errorHandler('[test 2] App should have permission: ' + PERMISSION_TYPE);
   }
 
   removeAppFrame(APP_IFRAME_ID);
 }
 
 function test3() {
   var afterGrandchildShutdown = function () {
@@ -106,18 +104,17 @@ function test3() {
                    embedApp.manifest.launch_path,
                    embedApp.manifestURL,
                    true);
 
   var appId = gAppsService.getAppLocalIdByManifestURL(APP_MANIFEST);
 
   if (!SpecialPowers.hasPermission(PERMISSION_TYPE,
                                    { url: APP_URL,
-                                     appId: appId,
-                                     isInBrowserElement: false })) {
+                                     originAttributes: { appId: appId }})) {
     errorHandler('[test 3] App should have permission: ' + PERMISSION_TYPE);
   }
 }
 
 function test4() {
   var afterGrandchildShutdown = function () {
     // We expect there is still a permission for the test-target-app
     // after killing test-target-app on 3rd-level process
@@ -143,18 +140,17 @@ function test4() {
                    APP_URL,
                    APP_MANIFEST,
                    true);
 
   var appId = gAppsService.getAppLocalIdByManifestURL(APP_MANIFEST);
 
   if (!SpecialPowers.hasPermission(PERMISSION_TYPE,
                                    { url: APP_URL,
-                                     appId: appId,
-                                     isInBrowserElement: false })) {
+                                     originAttributes: { appId: appId }})) {
     errorHandler('[test 4] App should have permission: ' + PERMISSION_TYPE);
   }
 }
 
 function test5() {
   var afterShutdown = function () {
     // We expect there is no permission for the test-target-app
     // after crashing its parent-process
@@ -169,18 +165,17 @@ function test5() {
                    embedApp.manifest.launch_path + '#' + encodeURIComponent('crash'),
                    embedApp.manifestURL,
                    true);
 
   var appId = gAppsService.getAppLocalIdByManifestURL(APP_MANIFEST);
 
   if (!SpecialPowers.hasPermission( PERMISSION_TYPE,
                                     { url: APP_URL,
-                                      appId: appId,
-                                      isInBrowserElement: false })) {
+                                      originAttributes: { appId: appId }})) {
     errorHandler('[test 5] App should have permission: ' + PERMISSION_TYPE);
   }
 
   // Crash the child-process on 2nd level after
   // the grandchild process on 3rd is allocated
   var handler = {'crash': function() {
       gScript.sendAsyncMessage("crashreporter-status", {});
 
@@ -285,31 +280,29 @@ function addPermissionToApp(appURL, mani
 
   // Add app's permission asynchronously
   SpecialPowers.pushPermissions([
       { "type":PERMISSION_TYPE,
         "allow": 1,
         "expireType":permManager.EXPIRE_SESSION,
         "expireTime":now + SESSION_PERSIST_MINUTES*60*1000,
         "context": { url: appURL,
-                     appId: appId,
-                     isInBrowserElement:false }
+                     originAttributes: { appId: appId } }
       }
     ], function() {
       runTests();
     });
 }
 
 function runNextIfAppHasPermission(round, expect, appURL, manifestURL) {
   var appId = gAppsService.getAppLocalIdByManifestURL(manifestURL);
 
   var hasPerm = SpecialPowers.hasPermission(PERMISSION_TYPE,
                                             { url: appURL,
-                                              appId: appId,
-                                              isInBrowserElement: false });
+                                              originAttributes: { appId: appId }});
   var result = (expect==hasPerm);
   if (result) {
     runTests();
   } else {
     errorHandler( '[test ' + round + '] App should ' + ((expect)? '':'NOT ') +
                   'have permission: ' + PERMISSION_TYPE);
   }
 }
--- a/dom/ipc/tests/test_permission_when_oop_app_crashes.html
+++ b/dom/ipc/tests/test_permission_when_oop_app_crashes.html
@@ -28,18 +28,17 @@ var tests = [
 
   // Install the app
   installApp,
 
   // Allow permission for embedApp to open mozbrowser and mozapp
   function() {
     var appId = gAppsService.getAppLocalIdByManifestURL(embedAppHostedManifestURL);
     var context = { url: embedApp.origin,
-                    appId: appId,
-                    isInBrowserElement: false };
+                    originAttributes: { appId: appId } };
     setupOpenAppPermission(context, runTests);
   },
 
   // +-----------------------------------------------+
   // | Test 5: Open a test-target-app on 3rd level   |
   // |         process then crash its parent-process |
   // +-----------------------------------------------+
   //
--- a/dom/tv/test/mochitest/head.js
+++ b/dom/tv/test/mochitest/head.js
@@ -17,18 +17,19 @@ function installApp(aTestToken, aTemplat
                           + aTestToken + '&template=' + aTemplate;
   var request = navigator.mozApps.install(hostedManifestURL);
   request.onerror = cbError;
   request.onsuccess = function() {
     gApp = request.result;
 
     var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
     SpecialPowers.addPermission("tv", true, { url: gApp.origin,
-                                              appId: appId,
-                                              isInBrowserElement: false });
+                                              originAttributes: {
+                                                appId: appId
+                                              }});
 
     runTest();
   }
 }
 
 function uninstallApp() {
   var request = navigator.mozApps.mgmt.uninstall(gApp);
   request.onerror = cbError;
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -985,16 +985,24 @@ nsDocumentViewer::LoadComplete(nsresult 
       nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
       nsIPrincipal *principal = d->NodePrincipal();
       os->NotifyObservers(d,
                           nsContentUtils::IsSystemPrincipal(principal) ?
                           "chrome-document-loaded" :
                           "content-document-loaded",
                           nullptr);
 
+      // Notify any devtools about the load.
+      RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+      if (timelines && timelines->HasConsumer(docShell)) {
+        timelines->AddMarkerForDocShell(
+          docShell, "document::Load", MarkerTracingType::TIMESTAMP);
+      }
+
       EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
       if (timing) {
         timing->NotifyLoadEventEnd();
       }
     }
   } else {
     // XXX: Should fire error event to the document...
   }
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -86,16 +86,17 @@
     <!-- App requires OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" android:required="true" />
 
     <application android:label="@string/moz_app_displayname"
                  android:icon="@drawable/icon"
                  android:logo="@drawable/logo"
                  android:name="@MOZ_ANDROID_APPLICATION_CLASS@"
                  android:hardwareAccelerated="true"
+                 android:allowBackup="false"
 # The preprocessor does not yet support arbitrary parentheses, so this cannot
 # be parenthesized thus to clarify that the logical AND operator has precedence:
 #   !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
 #if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
                  android:debuggable="true">
 #else
                  android:debuggable="false">
 #endif
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2568,22 +2568,34 @@ var NativeWindow = {
         if (uri)
           return uri.schemeIs("tel");
         return false;
       }
     },
 
     imageLocationCopyableContext: {
       matches: function imageLinkCopyableContextMatches(aElement) {
+        if (aElement instanceof Ci.nsIDOMHTMLImageElement) {
+          // The image is blocked by Tap-to-load Images
+          if (aElement.hasAttribute("data-ctv-src") && !aElement.hasAttribute("data-ctv-show")) {
+            return false;
+          }
+        }
         return (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI);
       }
     },
 
     imageSaveableContext: {
       matches: function imageSaveableContextMatches(aElement) {
+        if (aElement instanceof Ci.nsIDOMHTMLImageElement) {
+          // The image is blocked by Tap-to-load Images
+          if (aElement.hasAttribute("data-ctv-src") && !aElement.hasAttribute("data-ctv-show")) {
+            return false;
+          }
+        }
         if (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI) {
           // The image must be loaded to allow saving
           let request = aElement.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
           return (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE));
         }
         return false;
       }
     },
--- a/testing/eslint-plugin-mozilla/docs/index.rst
+++ b/testing/eslint-plugin-mozilla/docs/index.rst
@@ -8,16 +8,19 @@ Mozilla ESLint Plugin
 ``Cu.import("some/path/Blah.jsm")`` adds Blah to the global scope.
 
 ``import-headjs-globals`` imports globals from head.js and from any files that
 should be imported by head.js (as far as we can correctly resolve the path).
 
 ``mark-test-function-used`` simply marks test (the test method) as used. This
 avoids ESLint telling us that the function is never called.
 
+``var-only-at-top-level`` Marks all var declarations that are not at the top
+level invalid.
+
 +-------+-----------------------+
 | Possible values for all rules |
 +-------+-----------------------+
 | Value | Meaning               |
 +-------+-----------------------+
 | 0     | Deactivated           |
 +-------+-----------------------+
 | 1     | Warning               |
@@ -26,16 +29,18 @@ avoids ESLint telling us that the functi
 +-------+-----------------------+
 
 Example configuration::
 
    "rules": {
      "mozilla/components-imports": 1,
      "mozilla/import-headjs-globals": 1,
      "mozilla/mark-test-function-used": 1,
+     "mozilla/var-only-at-top-level": 1,
    }
 
 .. toctree::
    :maxdepth: 1
 
    components-imports
    import-headjs-globals
    mark-test-function-used
+   var-only-at-top-level
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/docs/var-only-at-top-level.rst
@@ -0,0 +1,10 @@
+.. _var-only-at-top-level:
+
+=======================
+var-only-at-top-level
+=======================
+
+Rule Details
+------------
+
+Marks all var declarations that are not at the top level invalid.
--- a/testing/eslint-plugin-mozilla/lib/index.js
+++ b/testing/eslint-plugin-mozilla/lib/index.js
@@ -10,16 +10,18 @@
 //------------------------------------------------------------------------------
 // Plugin Definition
 //------------------------------------------------------------------------------
 
 module.exports = {
   rules: {
     "components-imports": require("../lib/rules/components-imports"),
     "import-headjs-globals": require("../lib/rules/import-headjs-globals"),
-    "mark-test-function-used": require("../lib/rules/mark-test-function-used")
+    "mark-test-function-used": require("../lib/rules/mark-test-function-used"),
+    "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
   },
   rulesConfig: {
     "components-imports": 0,
     "import-headjs-globals": 0,
-    "mark-test-function-used": 0
+    "mark-test-function-used": 0,
+    "var-only-at-top-level": 0
   }
 };
--- a/testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
@@ -80,17 +80,17 @@ module.exports = function(context) {
   //--------------------------------------------------------------------------
   // Public
   //--------------------------------------------------------------------------
 
   return {
     Program: function(node) {
       var pathAndFilename = this.getFilename();
       var processPath = process.cwd();
-      var isTest = /.*\/browser_.+\.js$/.test(pathAndFilename);
+      var isTest = /.*[\\/]browser_.+\.js$/.test(pathAndFilename);
 
       if (!isTest) {
         return;
       }
 
       var testFilename = path.basename(pathAndFilename);
       var testPath = path.join(processPath, testFilename);
       var headjs = path.join(processPath, "head.js");
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Marks all var declarations that are not at the top level
+ *               invalid.
+ *
+ * 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";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+  //--------------------------------------------------------------------------
+  // Public
+  //--------------------------------------------------------------------------
+
+  return {
+    "VariableDeclaration": function(node) {
+      if (node.kind === "var") {
+        var ancestors = context.getAncestors();
+        var parent = ancestors.pop();
+
+        if (parent.type === "Program") {
+          return;
+        }
+
+        context.report(node, "Unexpected var, use let or const instead.");
+      }
+    }
+  };
+};
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushAppPermissions.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushAppPermissions.html
@@ -64,46 +64,44 @@ function installApp(aTestToken, aTemplat
     gApp = request.result; // Assign to global variable
     pushPermissionsToApp();
   }
 }
 
 function pushPermissionsToApp() {
   var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
   var context = { url: gApp.origin,
-                  appId: appId,
-                  isInBrowserElement: false };
+                  originAttributes: {appId: appId}};
   SpecialPowers.pushPermissions([
       { "type": "pAppPermission", "allow": true, "context": context }
     ], testPermissionsForApp);
 }
 
 function testPermissionsForApp() {
   var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
   var context = { url: gApp.origin,
-                  appId: appId,
-                  isInBrowserElement: false };
+                  originAttributes: {appId: appId}};
   ok(SpecialPowers.hasPermission('pAppPermission', context), 'pAppPermission should have permission');
   uninstallApp();
 }
 
 function uninstallApp() {
   var request = navigator.mozApps.mgmt.uninstall(gApp);
   request.onerror = cbError;
   request.onsuccess = function() {
     testPermissionsForSelfAndApp();
   }
 }
 
 function testPermissionsForSelfAndApp() {
   var appId = gAppsService.getAppLocalIdByManifestURL(gApp.manifestURL);
-  var context = { url: gApp.origin,
-                  appId: appId,
-                  isInBrowserElement: false };
-  ok(!SpecialPowers.hasPermission('pAppPermission', context), 'pAppPermission should not have permission');
+  is(appId, 0, "appId should become NO_APP_ID");
+  // since gApp is uninstalled, calling SpecialPowers.hasPermission with the
+  // app's properties (manifestURL, origin, principal, ... etc) will throw.
+  // So we don't need to test hasPermission for the app.
 
   ok(SpecialPowers.hasPermission('pAppPermission', document), 'pAppPermission should have permission');
 
   SimpleTest.finish();
 }
 </script>
 </pre>
 </body>
--- a/testing/mochitest/tests/Harness_sanity/test_bug816847.html
+++ b/testing/mochitest/tests/Harness_sanity/test_bug816847.html
@@ -52,35 +52,33 @@ function starttest(){
   var origin = app.origin 
   var nodePrincipal = SpecialPowers.wrap(document).nodePrincipal;
   var appPrincipal = createPrincipal(manifest, true);
   var noappPrincipal = createPrincipal(origin, false);
 
   SpecialPowers.addPermission(perms[0], true, origin);
   SpecialPowers.addPermission(perms[1], true, {manifestURL: manifest});
   SpecialPowers.addPermission(perms[2], true, document);
-  SpecialPowers.addPermission(perms[3], true, {url: origin,
-                                               appId: Ci.nsIScriptSecurityManager.NO_APP_ID});
+  SpecialPowers.addPermission(perms[3], true, {url: origin});
 
   is(Services.perms.testPermissionFromPrincipal(noappPrincipal, perms[0]),
      allow, "Set permission by string");
   is(Services.perms.testPermissionFromPrincipal(appPrincipal, perms[1]),
      allow, "Set permission by manifestURL");
   is(Services.perms.testPermissionFromPrincipal(nodePrincipal, perms[2]),
      allow, "Set permission by principal");
   is(Services.perms.testPermissionFromPrincipal(noappPrincipal, perms[3]),
      allow, "Set permission by other");
   is(Services.perms.testPermissionFromPrincipal(noappPrincipal, perms[1]),
      unknown, "Check that app permission doesn't leak to normal page");
 
   SpecialPowers.removePermission(perms[0], origin);
   SpecialPowers.removePermission(perms[1], {manifestURL: manifest});
   SpecialPowers.removePermission(perms[2], document);
-  SpecialPowers.removePermission(perms[3], {url: origin,
-                                            appId: Ci.nsIScriptSecurityManager.NO_APP_ID});
+  SpecialPowers.removePermission(perms[3], {url: origin});
 
   is(Services.perms.testPermissionFromPrincipal(noappPrincipal, perms[0]),
      unknown, "Removed permission by string");
   is(Services.perms.testPermissionFromPrincipal(appPrincipal, perms[1]),
      unknown, "Removed permission by manifestURL");
   is(Services.perms.testPermissionFromPrincipal(nodePrincipal, perms[2]),
      unknown, "Removed permission by principal");
   is(Services.perms.testPermissionFromPrincipal(noappPrincipal, perms[3]),
--- a/testing/specialpowers/components/SpecialPowersObserver.js
+++ b/testing/specialpowers/components/SpecialPowersObserver.js
@@ -229,17 +229,19 @@ SpecialPowersObserver.prototype = new Sp
           var permission = aSubject.QueryInterface(Ci.nsIPermission);
 
           // specialPowersAPI will consume this value, and it is used as a
           // fake permission, but only type and principal.appId will be used.
           //
           // We need to ensure that it looks the same as a real permission,
           // so we fake these properties.
           msg.permission = {
-            principal: { appId: permission.principal.appId },
+            principal: {
+              originAttributes: {appId: permission.principal.appId}
+            },
             type: permission.type
           };
         default:
           this._self._sendAsyncMessage("specialpowers-" + aTopic, msg);
       }
     }
   };
 
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -316,42 +316,31 @@ SpecialPowersObserverAPI.prototype = {
           default:
             throw new SpecialPowersError("Invalid operation for SPProcessCrashService");
         }
         return undefined;	// See comment at the beginning of this function.
       }
 
       case "SPPermissionManager": {
         let msg = aMessage.json;
-
-        let secMan = Services.scriptSecurityManager;
-        // TODO: Bug 1196665 - Add originAttributes into SpecialPowers
-        let attrs = {appId: msg.appId, inBrowser: msg.isInBrowserElement};
-        let principal = secMan.createCodebasePrincipal(this._getURI(msg.url), attrs);
+        let principal = msg.principal;
 
         switch (msg.op) {
           case "add":
             Services.perms.addFromPrincipal(principal, msg.type, msg.permission, msg.expireType, msg.expireTime);
             break;
           case "remove":
             Services.perms.removeFromPrincipal(principal, msg.type);
             break;
           case "has":
             let hasPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type);
-            if (hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION) 
-              return true;
-            return false;
-            break;
+            return hasPerm == Ci.nsIPermissionManager.ALLOW_ACTION;
           case "test":
             let testPerm = Services.perms.testPermissionFromPrincipal(principal, msg.type, msg.value);
-            if (testPerm == msg.value)  {
-              return true;
-            }
-            return false;
-            break;
+            return testPerm == msg.value;
           default:
             throw new SpecialPowersError(
               "Invalid operation for SPPermissionManager");
         }
         return undefined;	// See comment at the beginning of this function.
       }
 
       case "SPSetTestPluginEnabledState": {
@@ -505,28 +494,23 @@ SpecialPowersObserverAPI.prototype = {
       case 'SPQuotaManager': {
         let qm = Cc['@mozilla.org/dom/quota/manager;1']
                    .getService(Ci.nsIQuotaManager);
         let mm = aMessage.target
                          .QueryInterface(Ci.nsIFrameLoaderOwner)
                          .frameLoader
                          .messageManager;
         let msg = aMessage.data;
+        let principal = msg.principal;
         let op = msg.op;
 
         if (op != 'clear' && op != 'getUsage' && op != 'reset') {
           throw new SpecialPowersError('Invalid operation for SPQuotaManager');
         }
 
-        let secMan = Services.scriptSecurityManager;
-        let principal = secMan.createCodebasePrincipal(this._getURI(msg.uri), {
-          appId: msg.appId,
-          inBrowser: msg.inBrowser,
-        });
-
         if (op == 'clear') {
           qm.clearStoragesForPrincipal(principal);
         } else if (op == 'reset') {
           qm.reset();
         }
 
         // We always use the getUsageForPrincipal callback even if we're clearing
         // since we know that clear and getUsageForPrincipal are synchronized by the
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -781,18 +781,18 @@ SpecialPowersAPI.prototype = {
         } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_SESSION, context)) {
           originalValue = Ci.nsICookiePermission.ACCESS_SESSION;
         } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY, context)) {
           originalValue = Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY;
         } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY, context)) {
           originalValue = Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY;
         }
 
-        let [url, appId, isInBrowserElement, isSystem] = this._getInfoFromPermissionArg(context);
-        if (isSystem) {
+        let principal = this._getPrincipalFromArg(context);
+        if (principal.isSystemPrincipal) {
           continue;
         }
 
         let perm;
         if (typeof permission.allow !== 'boolean') {
           perm = permission.allow;
         } else {
           perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION
@@ -805,19 +805,17 @@ SpecialPowersAPI.prototype = {
         if (originalValue == perm) {
           continue;
         }
 
         var todo = {'op': 'add',
                     'type': permission.type,
                     'permission': perm,
                     'value': perm,
-                    'url': url,
-                    'appId': appId,
-                    'isInBrowserElement': isInBrowserElement,
+                    'principal': principal,
                     'expireType': (typeof permission.expireType === "number") ?
                       permission.expireType : 0, // default: EXPIRE_NEVER
                     'expireTime': (typeof permission.expireTime === "number") ?
                       permission.expireTime : 0};
 
         var cleanupTodo = Object.assign({}, todo);
 
         if (permission.remove == true)
@@ -962,17 +960,17 @@ SpecialPowersAPI.prototype = {
         }
       } else {
         var found = false;
         for (var i = 0; !found && i < this._self._permissionsUndoStack.length; i++) {
           var undos = this._self._permissionsUndoStack[i];
           for (var j = 0; j < undos.length; j++) {
             var undo = undos[j];
             if (undo.op == this._obsDataMap[aData] &&
-                undo.appId == permission.principal.appId &&
+                undo.principal.originAttributes.appId == permission.principal.originAttributes.appId &&
                 undo.type == permission.type) {
               // Remove this undo item if it has been done by others(not
               // specialpowers itself.)
               undos.splice(j,1);
               found = true;
               break;
             }
           }
@@ -1815,138 +1813,114 @@ SpecialPowersAPI.prototype = {
    * Get the message manager associated with an <iframe mozbrowser>.
    */
   getBrowserFrameMessageManager: function(aFrameElement) {
     return this.wrap(aFrameElement.QueryInterface(Ci.nsIFrameLoaderOwner)
                                   .frameLoader
                                   .messageManager);
   },
 
-  _getInfoFromPermissionArg: function(arg) {
-    let url = "";
-    let appId = Ci.nsIScriptSecurityManager.NO_APP_ID;
-    let isInBrowserElement = false;
-    let isSystem = false;
+  _getPrincipalFromArg: function(arg) {
+    let principal;
+    let secMan = Services.scriptSecurityManager;
 
     if (typeof(arg) == "string") {
       // It's an URL.
-      url = Cc["@mozilla.org/network/io-service;1"]
-              .getService(Ci.nsIIOService)
-              .newURI(arg, null, null)
-              .spec;
+      let uri = Services.io.newURI(arg, null, null);
+      principal = secMan.createCodebasePrincipal(uri, {});
     } else if (arg.manifestURL) {
       // It's a thing representing an app.
       let appsSvc = Cc["@mozilla.org/AppsService;1"]
                       .getService(Ci.nsIAppsService)
       let app = appsSvc.getAppByManifestURL(arg.manifestURL);
-
       if (!app) {
         throw "No app for this manifest!";
       }
 
-      appId = appsSvc.getAppLocalIdByManifestURL(arg.manifestURL);
-      url = app.origin;
-      isInBrowserElement = arg.isInBrowserElement || false;
+      principal = app.principal;
     } else if (arg.nodePrincipal) {
       // It's a document.
-      isSystem = (arg.nodePrincipal instanceof Ci.nsIPrincipal) &&
-                 Cc["@mozilla.org/scriptsecuritymanager;1"].
-                 getService(Ci.nsIScriptSecurityManager).
-                 isSystemPrincipal(arg.nodePrincipal);
-      if (!isSystem) {
-        // System principals don't have a URL associated with them, and they
-        // don't really need any permissions to be registered with the
-        // permission manager anyway.
-        url = arg.nodePrincipal.URI.spec;
-        appId = arg.nodePrincipal.appId;
-        isInBrowserElement = arg.nodePrincipal.isInBrowserElement;
-      }
+      // In some tests the arg is a wrapped DOM element, so we unwrap it first.
+      principal = unwrapIfWrapped(arg).nodePrincipal;
     } else {
-      url = arg.url;
-      appId = arg.appId;
-      isInBrowserElement = arg.isInBrowserElement;
+      let uri = Services.io.newURI(arg.url, null, null);
+      let attrs = arg.originAttributes || {};
+      principal = secMan.createCodebasePrincipal(uri, attrs);
     }
 
-    return [ url, appId, isInBrowserElement, isSystem ];
+    return principal;
   },
 
   addPermission: function(type, allow, arg, expireType, expireTime) {
-    let [url, appId, isInBrowserElement, isSystem] = this._getInfoFromPermissionArg(arg);
-    if (isSystem) {
+    let principal = this._getPrincipalFromArg(arg);
+    if (principal.isSystemPrincipal) {
       return; // nothing to do
     }
 
     let permission;
     if (typeof allow !== 'boolean') {
       permission = allow;
     } else {
       permission = allow ? Ci.nsIPermissionManager.ALLOW_ACTION
                          : Ci.nsIPermissionManager.DENY_ACTION;
     }
 
     var msg = {
       'op': 'add',
       'type': type,
       'permission': permission,
-      'url': url,
-      'appId': appId,
-      'isInBrowserElement': isInBrowserElement,
+      'principal': principal,
       'expireType': (typeof expireType === "number") ? expireType : 0,
       'expireTime': (typeof expireTime === "number") ? expireTime : 0
     };
 
     this._sendSyncMessage('SPPermissionManager', msg);
   },
 
   removePermission: function(type, arg) {
-    let [url, appId, isInBrowserElement, isSystem] = this._getInfoFromPermissionArg(arg);
-    if (isSystem) {
+    let principal = this._getPrincipalFromArg(arg);
+    if (principal.isSystemPrincipal) {
       return; // nothing to do
     }
 
     var msg = {
       'op': 'remove',
       'type': type,
-      'url': url,
-      'appId': appId,
-      'isInBrowserElement': isInBrowserElement
+      'principal': principal
     };
 
     this._sendSyncMessage('SPPermissionManager', msg);
   },
 
   hasPermission: function (type, arg) {
-    let [url, appId, isInBrowserElement, isSystem] = this._getInfoFromPermissionArg(arg);
-    if (isSystem) {
+    let principal = this._getPrincipalFromArg(arg);
+    if (principal.isSystemPrincipal) {
       return true; // system principals have all permissions
     }
 
     var msg = {
       'op': 'has',
       'type': type,
-      'url': url,
-      'appId': appId,
-      'isInBrowserElement': isInBrowserElement
+      'principal': principal
     };
 
     return this._sendSyncMessage('SPPermissionManager', msg)[0];
   },
+
   testPermission: function (type, value, arg) {
-    let [url, appId, isInBrowserElement, isSystem] = this._getInfoFromPermissionArg(arg);
-    if (isSystem) {
+    let principal = this._getPrincipalFromArg(arg);
+    if (principal.isSystemPrincipal) {
       return true; // system principals have all permissions
     }
 
     var msg = {
       'op': 'test',
       'type': type,
-      'value': value, 
-      'url': url,
-      'appId': appId,
-      'isInBrowserElement': isInBrowserElement
+      'value': value,
+      'principal': principal
     };
     return this._sendSyncMessage('SPPermissionManager', msg)[0];
   },
 
   isContentWindowPrivate: function(win) {
     return PrivateBrowsingUtils.isContentWindowPrivate(win);
   },
 
@@ -1961,38 +1935,30 @@ SpecialPowersAPI.prototype = {
     var msg = {
       'op': 'notify',
       'observerTopic': topic,
       'observerData': data
     };
     this._sendSyncMessage('SPObserverService', msg);
   },
 
-  clearStorageForURI: function(uri, callback, appId, inBrowser) {
-    this._quotaManagerRequest('clear', uri, appId, inBrowser, callback);
-  },
-
-  getStorageUsageForURI: function(uri, callback, appId, inBrowser) {
-    this._quotaManagerRequest('getUsage', uri, appId, inBrowser, callback);
+  clearStorageForDoc: function(wrappedDocument, callback) {
+    this._quotaManagerRequest('clear', wrappedDocument, callback);
   },
 
-  // Technically this restarts the QuotaManager for all URIs, but we need
-  // a specific one to perform the synchronized callback when the reset is
-  // complete.
-  resetStorageForURI: function(uri, callback, appId, inBrowser) {
-    this._quotaManagerRequest('reset', uri, appId, inBrowser, callback);
+  getStorageUsageForDoc: function(wrappedDocument, callback) {
+    this._quotaManagerRequest('getUsage', wrappedDocument, callback);
   },
 
-  _quotaManagerRequest: function(op, uri, appId, inBrowser, callback) {
-    const messageTopic = "SPQuotaManager";
+  resetStorageForDoc: function(wrappedDocument, callback) {
+    this._quotaManagerRequest('reset', wrappedDocument, callback);
+  },
 
-    if (uri instanceof Ci.nsIURI) {
-      uri = uri.spec;
-    }
-
+  _quotaManagerRequest: function(op, wrappedDocument, callback) {
+    const messageTopic = "SPQuotaManager";
     const id = Cc["@mozilla.org/uuid-generator;1"]
                  .getService(Ci.nsIUUIDGenerator)
                  .generateUUID()
                  .toString();
 
     let callbackInfo = { id: id, callback: callback };
 
     if (this._quotaManagerCallbackInfos) {
@@ -2019,17 +1985,18 @@ SpecialPowersAPI.prototype = {
           }
         }
       }.bind(this);
 
       this._addMessageListener(messageTopic, callbackInfo.listener);
       this._quotaManagerCallbackInfos = [ callbackInfo ];
     }
 
-    let msg = { op: op, uri: uri, appId: appId, inBrowser: inBrowser, id: id };
+    let principal = unwrapIfWrapped(wrappedDocument).nodePrincipal;
+    let msg = { op: op, principal: principal, id: id };
     this._sendAsyncMessage(messageTopic, msg);
   },
 
   createDOMFile: function(path, options) {
     return new File(path, options);
   },
 
   startPeriodicServiceWorkerUpdates: function() {
--- a/toolkit/components/alerts/resources/content/alert.js
+++ b/toolkit/components/alerts/resources/content/alert.js
@@ -126,18 +126,18 @@ function onAlertLoad() {
   window.addEventListener("XULAlertClose", function() { window.close(); });
 
   if (Services.prefs.getBoolPref("alerts.disableSlidingEffect")) {
     setTimeout(function() { window.close(); }, ALERT_DURATION_IMMEDIATE);
   } else {
     let alertBox = document.getElementById("alertBox");
     alertBox.addEventListener("animationend", function hideAlert(event) {
       if (event.animationName == "alert-animation" ||
-          event.animationName == "alert-zoom-animation" ||
-          event.animationName == "alert-fadeout-animation") {
+          event.animationName == "alert-clicked-animation" ||
+          event.animationName == "alert-closing-animation") {
         alertBox.removeEventListener("animationend", hideAlert, false);
         window.close();
       }
     }, false);
     alertBox.setAttribute("animate", true);
   }
 
   let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true});
--- a/toolkit/components/asyncshutdown/AsyncShutdown.jsm
+++ b/toolkit/components/asyncshutdown/AsyncShutdown.jsm
@@ -976,15 +976,16 @@ Barrier.prototype = Object.freeze({
 
 // List of well-known phases
 // Ideally, phases should be registered from the component that decides
 // when they start/stop. For compatibility with existing startup/shutdown
 // mechanisms, we register a few phases here.
 
 this.AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown");
 this.AsyncShutdown.profileBeforeChange = getPhase("profile-before-change");
+this.AsyncShutdown.placesClosingInternalConnection = getPhase("places-will-close-connection");
 this.AsyncShutdown.sendTelemetry = getPhase("profile-before-change2");
 this.AsyncShutdown.webWorkersShutdown = getPhase("web-workers-shutdown");
 this.AsyncShutdown.xpcomThreadsShutdown = getPhase("xpcom-threads-shutdown");
 
 this.AsyncShutdown.Barrier = Barrier;
 
 Object.freeze(this.AsyncShutdown);
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -2036,93 +2036,77 @@ XPCOMUtils.defineLazyGetter(PlacesUtils,
 
 XPCOMUtils.defineLazyGetter(this, "bundle", function() {
   const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
   return Cc["@mozilla.org/intl/stringbundle;1"].
          getService(Ci.nsIStringBundleService).
          createBundle(PLACES_STRING_BUNDLE_URI);
 });
 
-// A promise resolved once the Sqlite.jsm connections
-// can be closed.
-var promiseCanCloseConnection = function() {
-  let TOPIC = "places-will-close-connection";
-  return new Promise(resolve => {
-    let observer = function() {
-      Services.obs.removeObserver(observer, TOPIC);
-      resolve();
-    }
-    Services.obs.addObserver(observer, TOPIC, false)
-  });
-};
+/**
+ * Setup internal databases for closing properly during shutdown.
+ *
+ * 1. Places initiates shutdown.
+ * 2. Before places can move to the step where it closes the low-level connection,
+ *   we need to make sure that we have closed `conn`.
+ * 3. Before we can close `conn`, we need to make sure that all external clients
+ *   have stopped using `conn`.
+ * 4. Before we can close Sqlite, we need to close `conn`.
+ */
+function setupDbForShutdown(conn, name) {
+  try {
+    let state = "0. Not started.";
+    let promiseClosed = new Promise(resolve => {
+      // The service initiates shutdown.
+      // Before it can safely close its connection, we need to make sure
+      // that we have closed the high-level connection.
+      AsyncShutdown.placesClosingInternalConnection.addBlocker(`${name} closing as part of Places shutdown`,
+        Task.async(function*() {
+          state = "1. Service has initiated shutdown";
+
+          // At this stage, all external clients have finished using the
+          // database. We just need to close the high-level connection.
+          yield conn.close();
+          state = "2. Closed Sqlite.jsm connection.";
+
+          resolve();
+        }),
+        () => state
+      );
+    });
+
+    // Make sure that Sqlite.jsm doesn't close until we are done
+    // with the high-level connection.
+    Sqlite.shutdown.addBlocker(`${name} must be closed before Sqlite.jsm`,
+      () => promiseClosed,
+      () => state
+    );
+  } catch(ex) {
+    // It's too late to block shutdown, just close the connection.
+    conn.close();
+    throw ex;
+  }
+}
 
 XPCOMUtils.defineLazyGetter(this, "gAsyncDBConnPromised",
-  () => new Promise((resolve) => {
-    Sqlite.cloneStorageConnection({
-      connection: PlacesUtils.history.DBConnection,
-      readOnly:   true
-    }).then(conn => {
-      try {
-        let state = "0. not started";
-
-        let promiseReady = promiseCanCloseConnection();
-        let promiseShutdownComplete = Task.async(function*() {
-          // Don't close the connection as long as it might be used.
-          state = "1. waiting for `places-will-close-connection`";
-          yield promiseReady;
-
-          // But close the connection before Sqlite shutdown.
-          state = "2. closing the connection";
-          yield conn.close();
-
-          state = "3. done";
-        })();
-        Sqlite.shutdown.addBlocker("PlacesUtils read-only connection closing",
-          promiseShutdownComplete,
-          () => state);
-      } catch(ex) {
-        // It's too late to block shutdown, just close the connection.
-        conn.close();
-        throw ex;
-      }
-      resolve(conn);
-    });
+  () => Sqlite.cloneStorageConnection({
+    connection: PlacesUtils.history.DBConnection,
+    readOnly:   true
+  }).then(conn => {
+      setupDbForShutdown(conn, "PlacesUtils read-only connection");
+      return conn;
   })
 );
 
 XPCOMUtils.defineLazyGetter(this, "gAsyncDBWrapperPromised",
-  () => new Promise((resolve) => {
-    Sqlite.wrapStorageConnection({
+  () => Sqlite.wrapStorageConnection({
       connection: PlacesUtils.history.DBConnection,
-    }).then(conn => {
-      try {
-        let state = "0. not started";
-
-        let promiseReady = promiseCanCloseConnection();
-        let promiseShutdownComplete = Task.async(function*() {
-          // Don't close the connection as long as it might be used.
-          state = "1. waiting for `places-will-close-connection`";
-          yield promiseReady;
-
-          // But close the connection before Sqlite shutdown.
-          state = "2. closing the connection";
-          yield conn.close();
-
-          state = "3. done";
-        })();
-        Sqlite.shutdown.addBlocker("PlacesUtils wrapped connection closing",
-          promiseShutdownComplete,
-          () => state);
-      } catch(ex) {
-        // It's too late to block shutdown, just close the connection.
-        conn.close();
-        throw ex;
-      }
-      resolve(conn);
-    });
+  }).then(conn => {
+    setupDbForShutdown(conn, "PlacesUtils wrapped connection");
+    return conn;
   })
 );
 
 /**
  * Keywords management API.
  * Sooner or later these keywords will merge with search keywords, this is an
  * interim API that should then be replaced by a unified one.
  * Keywords are associated with URLs and can have POST data.
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2880,66 +2880,87 @@ CreateJSHangStack(JSContext* cx, const T
     JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i]));
     if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) {
       return nullptr;
     }
   }
   return ret;
 }
 
-static JSObject*
-CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations)
+static void
+CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations,
+                        JS::MutableHandleObject returnedObject)
 {
   JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0));
   if (!annotationsArray) {
-    return nullptr;
-  }
+    returnedObject.set(nullptr);
+    return;
+  }
+  // We keep track of the annotations we reported in this hash set, so we can
+  // discard duplicated ones.
+  nsTHashtable<nsStringHashKey> reportedAnnotations;
   size_t annotationIndex = 0;
   for (const HangAnnotationsPtr *i = annotations.begin(), *e = annotations.end();
        i != e; ++i) {
     JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx));
     if (!jsAnnotation) {
       continue;
     }
     const HangAnnotationsPtr& curAnnotations = *i;
+    // Build a key to index the current annotations in our hash set.
+    nsAutoString annotationsKey;
+    nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+    // Check if the annotations are in the set. If that's the case, don't double report.
+    if (reportedAnnotations.GetEntry(annotationsKey)) {
+      continue;
+    }
+    // If not, report them.
+    reportedAnnotations.PutEntry(annotationsKey);
     UniquePtr<HangAnnotations::Enumerator> annotationsEnum =
       curAnnotations->GetEnumerator();
     if (!annotationsEnum) {
       continue;
     }
     nsAutoString key;
     nsAutoString value;
     while (annotationsEnum->Next(key, value)) {
       JS::RootedValue jsValue(cx);
       jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
       if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
                                jsValue, JSPROP_ENUMERATE)) {
-        return nullptr;
+        returnedObject.set(nullptr);
+        return;
       }
     }
     if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) {
       continue;
     }
     ++annotationIndex;
   }
-  return annotationsArray;
+  // Return the array using a |MutableHandleObject| to avoid triggering a false
+  // positive rooting issue in the hazard analysis build.
+  returnedObject.set(annotationsArray);
 }
 
 static JSObject*
 CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
 {
   JS::RootedObject ret(cx, JS_NewPlainObject(cx));
   if (!ret) {
     return nullptr;
   }
 
   JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack()));
   JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
   auto& hangAnnotations = hang.GetAnnotations();
-  JS::RootedObject annotations(cx, CreateJSHangAnnotations(cx, hangAnnotations));
+  JS::RootedObject annotations(cx);
+  CreateJSHangAnnotations(cx, hangAnnotations, &annotations);
 
   if (!stack ||
       !time ||
       !annotations ||
       !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
       !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
       (!hangAnnotations.empty() && // <-- Only define annotations when nonempty
         !JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
--- a/toolkit/themes/osx/global/alerts/alert.css
+++ b/toolkit/themes/osx/global/alerts/alert.css
@@ -17,61 +17,14 @@
 
 #alertBox {
   border: 1px solid ThreeDShadow;
   border-radius: 1px;
   background-color: -moz-Dialog;
   color: -moz-DialogText;
 }
 
-#alertBox[animate] {
-  animation-timing-function: cubic-bezier(.12,1.23,.48,1.09);
-}
-
-#alertBox[animate][clicked] {
-  animation-duration: .6s;
-  animation-name: alert-zoom-animation;
-}
-
-/* This is used if the close button is clicked
-   before the animation has finished. */
-#alertBox[animate][closing] {
-  animation-duration: .6s;
-  animation-name: alert-fadeout-animation;
-}
-
-@keyframes alert-animation {
-  from {
-    transform: translate(110%, 0) rotate(-20deg);
-  }
-  5% {
-    transform: none;
-  }
-  95% {
-    transform: none;
-    opacity: 1;
-  }
-  to {
-    transform: translate(0, 60px) rotate(15deg);
-    opacity: 0;
-  }
-}
-
-@keyframes alert-zoom-animation {
-  to {
-    transform: scale(1.5);
-    opacity: 0;
-  }
-}
-
-@keyframes alert-fadeout-animation {
-  to {
-    transform: translate(0, 60px) rotate(15deg);
-    opacity: 0;
-  }
-}
-
 .alertCloseButton {
   -moz-appearance: none;
   padding: 0;
   margin: 2px;
   border: none;
 }
--- a/toolkit/themes/shared/alert-common.css
+++ b/toolkit/themes/shared/alert-common.css
@@ -15,16 +15,63 @@
   padding-inline-start: 90px;
 }
 
 #alertBox[hasBodyText] > #alertTextBox,
 #alertBox[hasOrigin] > #alertTitleBox {
   border-bottom: 1px solid ThreeDShadow;
 }
 
+#alertBox[animate] {
+  animation-timing-function: cubic-bezier(.12,1.23,.48,1.09);
+}
+
+#alertBox[animate][clicked] {
+  animation-duration: .6s;
+  animation-name: alert-clicked-animation;
+}
+
+/* This is used if the close button is clicked
+   before the animation has finished. */
+#alertBox[animate][closing] {
+  animation-duration: .6s;
+  animation-name: alert-closing-animation;
+}
+
+@keyframes alert-animation {
+  from {
+    transform: translate(110%, 0) rotate(-20deg);
+  }
+  5% {
+    transform: none;
+  }
+  95% {
+    transform: none;
+    opacity: 1;
+  }
+  to {
+    transform: translate(0, 60px) rotate(15deg);
+    opacity: 0;
+  }
+}
+
+@keyframes alert-clicked-animation {
+  to {
+    transform: scale(1.5);
+    opacity: 0;
+  }
+}
+
+@keyframes alert-closing-animation {
+  to {
+    transform: translate(0, 60px) rotate(15deg);
+    opacity: 0;
+  }
+}
+
 #alertImage {
   margin: 8px 0;
   width: 64px;
 }
 
 .alertTextBox {
   padding-top: 8px;
   padding-inline-start: 8px;
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -38,17 +38,17 @@
   --in-content-primary-button-background-active: #006b9d;
   --in-content-table-border-dark-color: #d1d1d1;
   --in-content-table-header-background: #0095dd;
   --in-content-help-button-background: #ffcb00;
   --in-content-help-button-background-hover: #f4c200;
   --in-content-help-button-background-active: #eaba00;
 }
 
-html|body,
+html|html,
 xul|page,
 xul|window {
   font: message-box;
   -moz-appearance: none;
   background-color: var(--in-content-page-background);
   color: var(--in-content-page-color);
 }
 
--- a/toolkit/themes/windows/global/alerts/alert.css
+++ b/toolkit/themes/windows/global/alerts/alert.css
@@ -12,63 +12,16 @@
 
 #alertBox {
   border: 1px solid ThreeDShadow;
   border-radius: 1px;
   background-color: -moz-Dialog;
   color: -moz-DialogText;
 }
 
-#alertBox[animate] {
-  animation-timing-function: cubic-bezier(.12,1.23,.48,1.09);
-}
-
-#alertBox[animate][clicked] {
-  animation-duration: .6s;
-  animation-name: alert-zoom-animation;
-}
-
-/* This is used if the close button is clicked
-   before the animation has finished. */
-#alertBox[animate][closing] {
-  animation-duration: .6s;
-  animation-name: alert-fadeout-animation;
-}
-
-@keyframes alert-animation {
-  from {
-    transform: translate(110%, 0) rotate(-20deg);
-  }
-  5% {
-    transform: none;
-  }
-  95% {
-    transform: none;
-    opacity: 1;
-  }
-  to {
-    transform: translate(0, 60px) rotate(15deg);
-    opacity: 0;
-  }
-}
-
-@keyframes alert-zoom-animation {
-  to {
-    transform: scale(1.5);
-    opacity: 0;
-  }
-}
-
-@keyframes alert-fadeout-animation {
-  to {
-    transform: translate(0, 60px) rotate(15deg);
-    opacity: 0;
-  }
-}
-
 .alertCloseButton {
   -moz-appearance: none;
   padding: 4px 2px;
   border: none !important;
 }
 
 @media (-moz-windows-default-theme) {
   #alertBox[hasBodyText] > #alertTextBox,