Merge m-c to oak
authorRobert Strong <robert.bugzilla@gmail.com>
Wed, 28 Oct 2015 09:39:52 -0700
changeset 491487 554128f7e7de86fc65c4c151344d46c2770eaf40
parent 491486 e2ffa9afadd208a89eca91aa4339d33954525af8 (current diff)
parent 304028 fc706d376f0658e560a59c3dd520437b18e8c4a4 (diff)
child 491488 c08a4b97795fd0c66821614a516d66932e0f9291
push id47343
push userbmo:dothayer@mozilla.com
push dateWed, 01 Mar 2017 22:58:58 +0000
milestone44.0a1
Merge m-c to oak
b2g/config/aries-dogfood/releng-aries-dogfood.tt
b2g/config/aries-spark-ota/releng-aries-spark-ota.tt
b2g/config/dolphin-512/releng-dolphin-512.tt
b2g/config/flame-kk-ota/releng-flame-kk-ota.tt
configure.in
devtools/client/memory/modules/census-view.js
devtools/client/memory/modules/moz.build
devtools/client/memory/test/mochitest/chrome.ini
devtools/client/memory/test/mochitest/head.js
devtools/client/memory/test/mochitest/test_census-view-01.html
image/BMPFileHeaders.h
js/src/tests/js1_6/extensions/regress-352392.js
mobile/android/tests/background/junit3/src/common/TestAndroidLogWriters.java
mobile/android/tests/background/junit3/src/common/TestBrowserContractHelpers.java
mobile/android/tests/background/junit3/src/common/TestDateUtils.java
mobile/android/tests/background/junit3/src/common/TestUtils.java
mobile/android/tests/background/junit3/src/common/TestWaitHelper.java
mobile/android/tests/background/junit3/src/db/AndroidBrowserRepositoryTestCase.java
mobile/android/tests/background/junit3/src/db/TestAndroidBrowserBookmarksRepository.java
mobile/android/tests/background/junit3/src/db/TestAndroidBrowserHistoryDataExtender.java
mobile/android/tests/background/junit3/src/db/TestAndroidBrowserHistoryRepository.java
mobile/android/tests/background/junit3/src/db/TestBookmarks.java
mobile/android/tests/background/junit3/src/db/TestCachedSQLiteOpenHelper.java
mobile/android/tests/background/junit3/src/db/TestClientsDatabase.java
mobile/android/tests/background/junit3/src/db/TestClientsDatabaseAccessor.java
mobile/android/tests/background/junit3/src/db/TestFennecTabsRepositorySession.java
mobile/android/tests/background/junit3/src/db/TestFennecTabsStorage.java
mobile/android/tests/background/junit3/src/db/TestFormHistoryRepositorySession.java
mobile/android/tests/background/junit3/src/db/TestPasswordsRepository.java
mobile/android/tests/background/junit3/src/fxa/TestAccountLoader.java
mobile/android/tests/background/junit3/src/fxa/TestBrowserIDKeyPairGeneration.java
mobile/android/tests/background/junit3/src/fxa/TestFirefoxAccounts.java
mobile/android/tests/background/junit3/src/fxa/authenticator/TestAccountPickler.java
mobile/android/tests/background/junit3/src/healthreport/MockDatabaseEnvironment.java
mobile/android/tests/background/junit3/src/healthreport/MockHealthReportDatabaseStorage.java
mobile/android/tests/background/junit3/src/healthreport/MockHealthReportSQLiteOpenHelper.java
mobile/android/tests/background/junit3/src/healthreport/MockProfileInformationCache.java
mobile/android/tests/background/junit3/src/healthreport/TestEnvironmentBuilder.java
mobile/android/tests/background/junit3/src/healthreport/TestEnvironmentV1HashAppender.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportBroadcastService.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportDatabaseStorage.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportGenerator.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportProvider.java
mobile/android/tests/background/junit3/src/healthreport/TestHealthReportSQLiteOpenHelper.java
mobile/android/tests/background/junit3/src/healthreport/TestProfileInformationCache.java
mobile/android/tests/background/junit3/src/healthreport/prune/TestHealthReportPruneService.java
mobile/android/tests/background/junit3/src/healthreport/prune/TestPrunePolicyDatabaseStorage.java
mobile/android/tests/background/junit3/src/healthreport/upload/TestAndroidSubmissionClient.java
mobile/android/tests/background/junit3/src/healthreport/upload/TestHealthReportUploadService.java
mobile/android/tests/background/junit3/src/helpers/AndroidSyncTestCase.java
mobile/android/tests/background/junit3/src/helpers/BackgroundServiceTestCase.java
mobile/android/tests/background/junit3/src/helpers/DBHelpers.java
mobile/android/tests/background/junit3/src/helpers/DBProviderTestCase.java
mobile/android/tests/background/junit3/src/helpers/FakeProfileTestCase.java
mobile/android/tests/background/junit3/src/nativecode/test/TestNativeCrypto.java
mobile/android/tests/background/junit3/src/sync/AndroidSyncTestCaseWithAccounts.java
mobile/android/tests/background/junit3/src/sync/TestAccountPickler.java
mobile/android/tests/background/junit3/src/sync/TestClientsStage.java
mobile/android/tests/background/junit3/src/sync/TestConfigurationMigrator.java
mobile/android/tests/background/junit3/src/sync/TestResetting.java
mobile/android/tests/background/junit3/src/sync/TestSendTabData.java
mobile/android/tests/background/junit3/src/sync/TestStoreTracking.java
mobile/android/tests/background/junit3/src/sync/TestSyncAccounts.java
mobile/android/tests/background/junit3/src/sync/TestSyncAuthenticatorService.java
mobile/android/tests/background/junit3/src/sync/TestSyncConfiguration.java
mobile/android/tests/background/junit3/src/sync/TestTabsRecord.java
mobile/android/tests/background/junit3/src/sync/TestUpgradeRequired.java
mobile/android/tests/background/junit3/src/sync/TestWebURLFinder.java
mobile/android/tests/background/junit3/src/sync/helpers/BookmarkHelpers.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultBeginDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultCleanDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultFetchDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultFinishDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultGuidsSinceDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultSessionCreationDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/DefaultStoreDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectBeginDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectBeginFailDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectFetchDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectFetchSinceDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectFinishDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectFinishFailDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectGuidsSinceDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectInvalidRequestFetchDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectInvalidTypeStoreDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectManyStoredDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectNoGUIDsSinceDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectNoStoreDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectStoreCompletedDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/ExpectStoredDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/HistoryHelpers.java
mobile/android/tests/background/junit3/src/sync/helpers/PasswordHelpers.java
mobile/android/tests/background/junit3/src/sync/helpers/SessionTestHelper.java
mobile/android/tests/background/junit3/src/sync/helpers/SimpleSuccessBeginDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/SimpleSuccessCreationDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/SimpleSuccessFetchDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/SimpleSuccessFinishDelegate.java
mobile/android/tests/background/junit3/src/sync/helpers/SimpleSuccessStoreDelegate.java
mobile/android/tests/background/junit3/src/telemetry/TestTelemetryRecorder.java
mobile/android/tests/background/junit3/src/testhelpers/BaseMockServerSyncStage.java
mobile/android/tests/background/junit3/src/testhelpers/CommandHelpers.java
mobile/android/tests/background/junit3/src/testhelpers/DefaultGlobalSessionCallback.java
mobile/android/tests/background/junit3/src/testhelpers/JPakeNumGeneratorFixed.java
mobile/android/tests/background/junit3/src/testhelpers/MockAbstractNonRepositorySyncStage.java
mobile/android/tests/background/junit3/src/testhelpers/MockClientsDataDelegate.java
mobile/android/tests/background/junit3/src/testhelpers/MockClientsDatabaseAccessor.java
mobile/android/tests/background/junit3/src/testhelpers/MockGlobalSession.java
mobile/android/tests/background/junit3/src/testhelpers/MockPrefsGlobalSession.java
mobile/android/tests/background/junit3/src/testhelpers/MockRecord.java
mobile/android/tests/background/junit3/src/testhelpers/MockServerSyncStage.java
mobile/android/tests/background/junit3/src/testhelpers/MockSharedPreferences.java
mobile/android/tests/background/junit3/src/testhelpers/StubDelegate.java
mobile/android/tests/background/junit3/src/testhelpers/WBORepository.java
mobile/android/tests/background/junit3/src/testhelpers/WaitHelper.java
testing/runcppunittests.py
toolkit/mozapps/update/updater/updater.cpp
--- a/addon-sdk/source/test/test-sandbox.js
+++ b/addon-sdk/source/test/test-sandbox.js
@@ -79,23 +79,16 @@ exports['test exceptions'] = function(as
     } + '();', 'foo.js', 2);
   }
   catch (error) {
     assert.equal(error.fileName, 'foo.js', 'correct fileName reported');
     assert.equal(error.lineNumber, 4, 'line number was opted');
   }
 };
 
-exports['test opt version'] = function(assert) {
-  let fixture = sandbox();
-  assert.throws(function() {
-    evaluate(fixture, 'let a = 2;', 'test.js', 1, '1.5');
-  }, 'No let in js 1.5');
-};
-
 exports['test load'] = function(assert) {
   let fixture = sandbox();
   load(fixture, fixturesURI + 'sandbox-normal.js');
   assert.equal(fixture.a, 1, 'global variable defined');
   assert.equal(fixture.b, 2, 'global via `this` property was set');
   assert.equal(fixture.f(), 4, 'function was defined');
 };
 
deleted file mode 100644
--- a/b2g/config/aries-dogfood/releng-aries-dogfood.tt
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-{
-"size": 80458572,
-"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
-}
-]
deleted file mode 100644
--- a/b2g/config/aries-spark-ota/releng-aries-spark-ota.tt
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-{
-"size": 80458572,
-"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
-}
-]
--- a/b2g/config/aries/config.json
+++ b/b2g/config/aries/config.json
@@ -34,17 +34,17 @@
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "B2G_UPDATE_CHANNEL": "nightly",
         "GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
     },
     "b2g_manifest": "aries.xml",
     "b2g_manifest_intree": true,
-    "additional_source_tarballs": [],
+    "additional_source_tarballs": ["backup-aries.tar.xz"],
     "gecko_l10n_root": "https://hg.mozilla.org/l10n-central",
     "gaia": {
         "l10n": {
             "vcs": "hgtool",
             "root": "https://hg.mozilla.org/gaia-l10n"
         }
     }
 }
--- a/b2g/config/aries/releng-aries.tt
+++ b/b2g/config/aries/releng-aries.tt
@@ -1,16 +1,16 @@
 [
 {
+"size": 135359412,
+"digest": "45e677c9606cc4eec44ef4761df47ff431df1ffad17a5c6d21ce700a1c47f79e87a4aa9f30ae47ff060bd64f5b775d995780d88211f9a759ffa0d076beb4816b",
+"algorithm": "sha512",
+"filename": "backup-aries.tar.xz",
+"comment": "v18D"
+},
+{
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
 }
 ]
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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"/>
deleted file mode 100644
--- a/b2g/config/dolphin-512/releng-dolphin-512.tt
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-{
-"size": 80458572,
-"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
-}
-]
--- a/b2g/config/dolphin/releng-dolphin.tt
+++ b/b2g/config/dolphin/releng-dolphin.tt
@@ -1,16 +1,9 @@
 [
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
 }
 ]
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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"/>
--- 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="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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="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"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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="1837d370a964a9719160c79155a07980f2ea4bdf"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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="1837d370a964a9719160c79155a07980f2ea4bdf"/>
--- 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="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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 -->
deleted file mode 100644
--- a/b2g/config/flame-kk-ota/releng-flame-kk-ota.tt
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-{
-"size": 80458572,
-"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
-"algorithm": "sha512",
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
-}
-]
--- a/b2g/config/flame-kk/releng-flame-kk.tt
+++ b/b2g/config/flame-kk/releng-flame-kk.tt
@@ -7,17 +7,10 @@
 "comment": "v18D"
 },
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
 }
 ]
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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"/>
--- a/b2g/config/flame/releng-flame.tt
+++ b/b2g/config/flame/releng-flame.tt
@@ -5,17 +5,10 @@
 "algorithm": "sha512"
 },
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
 }
 ]
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "a26eadc5e1133d5112b6cbc10badbb7670a1090f", 
+        "git_revision": "2e89362de40a6c9c36525d36317fa1ae8e67e143", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "a99ff14b3258f49f5902775a5e3b849f3455714a", 
+    "revision": "4a596a387a18b019fc2763b3509940d52154d72c", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/releng-mako.tt
+++ b/b2g/config/nexus-4-kk/releng-mako.tt
@@ -18,18 +18,11 @@
 "filename": "lge-mako-kot49h-f59c98be.tgz"
 },
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
 }
 ]
 
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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"/>
--- a/b2g/config/nexus-4/releng-mako.tt
+++ b/b2g/config/nexus-4/releng-mako.tt
@@ -18,18 +18,11 @@
 "filename": "lge-mako-jwr66v-985845e4.tgz"
 },
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
 }
 ]
 
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -13,17 +13,17 @@
   <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="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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="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"/>
--- a/b2g/config/nexus-5-l/releng-nexus5.tt
+++ b/b2g/config/nexus-5-l/releng-nexus5.tt
@@ -1,15 +1,7 @@
 [{
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
-},
-{
-"size": 12057960,
-"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
-"algorithm": "sha512",
-"filename": "gtk3.tar.xz",
-"unpack": true
-}
-]
+}]
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a26eadc5e1133d5112b6cbc10badbb7670a1090f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="2e89362de40a6c9c36525d36317fa1ae8e67e143"/>
   <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"/>
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -571,16 +571,22 @@ ContentSearchUIController.prototype = {
       this.input.setAttribute("aria-expanded", "true");
       this._originalDefaultEngine = {
         name: this.defaultEngine.name,
         icon: this.defaultEngine.icon,
       };
     }
   },
 
+  _onMsgSuggestionsCancelled: function () {
+    if (!this._table.hidden) {
+      this._hideSuggestions();
+    }
+  },
+
   _onMsgState: function (state) {
     this.engines = state.engines;
     // No point updating the default engine (and the header) if there's no change.
     if (this.defaultEngine &&
         this.defaultEngine.name == state.currentEngine.name &&
         this.defaultEngine.icon == state.currentEngine.icon) {
       return;
     }
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -416,23 +416,27 @@ loop.panel = (function(_, mozL10n) {
     },
 
     render: function() {
       var roomClasses = React.addons.classSet({
         "room-entry": true,
         "room-active": this._isActive()
       });
 
+      var roomTitle = this.props.room.decryptedContext.roomName ||
+        this.props.room.decryptedContext.urls[0].description ||
+        this.props.room.decryptedContext.urls[0].location;
+
       return (
         React.createElement("div", {className: roomClasses, 
           onClick: this.handleClickEntry, 
           onMouseLeave: this._handleMouseOut, 
           ref: "roomEntry"}, 
           React.createElement("h2", null, 
-            this.props.room.decryptedContext.roomName
+            roomTitle
           ), 
           React.createElement(RoomEntryContextItem, {
             mozLoop: this.props.mozLoop, 
             roomUrls: this.props.room.decryptedContext.urls}), 
           React.createElement(RoomEntryContextButtons, {
             dispatcher: this.props.dispatcher, 
             eventPosY: this.state.eventPosY, 
             handleClickEntry: this.handleClickEntry, 
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -416,23 +416,27 @@ loop.panel = (function(_, mozL10n) {
     },
 
     render: function() {
       var roomClasses = React.addons.classSet({
         "room-entry": true,
         "room-active": this._isActive()
       });
 
+      var roomTitle = this.props.room.decryptedContext.roomName ||
+        this.props.room.decryptedContext.urls[0].description ||
+        this.props.room.decryptedContext.urls[0].location;
+
       return (
         <div className={roomClasses}
           onClick={this.handleClickEntry}
           onMouseLeave={this._handleMouseOut}
           ref="roomEntry">
           <h2>
-            {this.props.room.decryptedContext.roomName}
+            {roomTitle}
           </h2>
           <RoomEntryContextItem
             mozLoop={this.props.mozLoop}
             roomUrls={this.props.room.decryptedContext.urls} />
           <RoomEntryContextButtons
             dispatcher={this.props.dispatcher}
             eventPosY={this.state.eventPosY}
             handleClickEntry={this.handleClickEntry}
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -750,18 +750,21 @@ loop.roomViews = (function(mozL10n) {
       // Handle timestamp and window closing only when the call has terminated.
       if (prevState.roomState === ROOM_STATES.ENDED &&
           this.state.roomState === ROOM_STATES.ENDED) {
         this.props.onCallTerminated();
       }
     },
 
     render: function() {
-      if (this.state.roomName) {
-        this.setTitle(this.state.roomName);
+      if (this.state.roomName || this.state.roomContextUrls) {
+        var roomTitle = this.state.roomName ||
+                        this.state.roomContextUrls[0].description ||
+                        this.state.roomContextUrls[0].location;
+        this.setTitle(roomTitle);
       }
 
       var screenShareData = {
         state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
         visible: true
       };
 
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -750,18 +750,21 @@ loop.roomViews = (function(mozL10n) {
       // Handle timestamp and window closing only when the call has terminated.
       if (prevState.roomState === ROOM_STATES.ENDED &&
           this.state.roomState === ROOM_STATES.ENDED) {
         this.props.onCallTerminated();
       }
     },
 
     render: function() {
-      if (this.state.roomName) {
-        this.setTitle(this.state.roomName);
+      if (this.state.roomName || this.state.roomContextUrls) {
+        var roomTitle = this.state.roomName ||
+                        this.state.roomContextUrls[0].description ||
+                        this.state.roomContextUrls[0].location;
+        this.setTitle(roomTitle);
       }
 
       var screenShareData = {
         state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
         visible: true
       };
 
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
--- a/browser/components/loop/content/shared/js/textChatStore.js
+++ b/browser/components/loop/content/shared/js/textChatStore.js
@@ -157,19 +157,24 @@ loop.store.TextChatStore = (function() {
      * so it can be added to the list.
      *
      * @param  {sharedActions.UpdateRoomInfo} actionData
      */
     updateRoomInfo: function(actionData) {
       // XXX When we add special messages to desktop, we'll need to not post
       // multiple changes of room name, only the first. Bug 1171940 should fix this.
       if (actionData.roomName) {
+        var roomName = actionData.roomName;
+        if (!roomName && actionData.roomContextUrls && actionData.roomContextUrls.length) {
+          roomName = actionData.roomContextUrls[0].description ||
+                     actionData.roomContextUrls[0].url;
+        }
         this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
           contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
-          message: actionData.roomName
+          message: roomName
         });
       }
 
       // Append the context if we have any.
       if (("roomContextUrls" in actionData) && actionData.roomContextUrls &&
           actionData.roomContextUrls.length) {
         // We only support the first url at the moment.
         var urlData = actionData.roomContextUrls[0];
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -97,25 +97,28 @@ loop.standaloneRoomViews = (function(moz
 
     _renderFailureText: function() {
       return (
         React.createElement("p", {className: "failure"}, mozL10n.get("rooms_already_joined"))
       );
     },
 
     render: function() {
+      var roomName = this.state.roomName ||
+                     this.state.roomContextUrls[0].description ||
+                     this.state.roomContextUrls[0].location;
       // The extra scroller div here is for providing a scroll view for shorter
       // screens, as the common.css specifies overflow:hidden for the body which
       // we need in some places.
       return (
         React.createElement("div", {className: "handle-user-agent-view-scroller"}, 
           React.createElement("div", {className: "handle-user-agent-view"}, 
             React.createElement("div", {className: "info-panel"}, 
               React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2")}), 
-              React.createElement("p", {className: "roomName"}, this.state.roomName), 
+              React.createElement("p", {className: "roomName"}, roomName), 
               React.createElement("p", {className: "loop-logo"}), 
               
                 this.state.failureReason ?
                   this._renderFailureText() :
                   this._renderJoinButton()
               
             ), 
             React.createElement(ToSView, {dispatcher: this.props.dispatcher}), 
@@ -439,26 +442,25 @@ loop.standaloneRoomViews = (function(moz
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
       if (this.state.roomState !== ROOM_STATES.READY &&
           nextState.roomState === ROOM_STATES.READY) {
-        var roomName = nextState.roomName || this.state.roomName;
+        var roomName = nextState.roomName ||
+                       this.state.roomName ||
+                       this.state.roomContextUrls[0].description ||
+                       this.state.roomContextUrls[0].location;
 
-        if (roomName) {
-          this.setTitle(mozL10n.get("standalone_title_with_room_name", {
-            roomName: roomName,
-            clientShortname: mozL10n.get("clientShortname2")
-          }));
-        } else {
-          this.setTitle(mozL10n.get("clientShortname2"));
-        }
+        this.setTitle(mozL10n.get("standalone_title_with_room_name", {
+          roomName: roomName,
+          clientShortname: mozL10n.get("clientShortname2")
+        }));
       }
 
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({ publishVideo: true })
         }));
       }
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -97,25 +97,28 @@ loop.standaloneRoomViews = (function(moz
 
     _renderFailureText: function() {
       return (
         <p className="failure">{mozL10n.get("rooms_already_joined")}</p>
       );
     },
 
     render: function() {
+      var roomName = this.state.roomName ||
+                     this.state.roomContextUrls[0].description ||
+                     this.state.roomContextUrls[0].location;
       // The extra scroller div here is for providing a scroll view for shorter
       // screens, as the common.css specifies overflow:hidden for the body which
       // we need in some places.
       return (
         <div className="handle-user-agent-view-scroller">
           <div className="handle-user-agent-view">
             <div className="info-panel">
               <p className="loop-logo-text" title={mozL10n.get("clientShortname2")}></p>
-              <p className="roomName">{this.state.roomName}</p>
+              <p className="roomName">{roomName}</p>
               <p className="loop-logo" />
               {
                 this.state.failureReason ?
                   this._renderFailureText() :
                   this._renderJoinButton()
               }
             </div>
             <ToSView dispatcher={this.props.dispatcher} />
@@ -439,26 +442,25 @@ loop.standaloneRoomViews = (function(moz
      * user media access.
      *
      * @param  {Object} nextProps (Unused)
      * @param  {Object} nextState Next state object.
      */
     componentWillUpdate: function(nextProps, nextState) {
       if (this.state.roomState !== ROOM_STATES.READY &&
           nextState.roomState === ROOM_STATES.READY) {
-        var roomName = nextState.roomName || this.state.roomName;
+        var roomName = nextState.roomName ||
+                       this.state.roomName ||
+                       this.state.roomContextUrls[0].description ||
+                       this.state.roomContextUrls[0].location;
 
-        if (roomName) {
-          this.setTitle(mozL10n.get("standalone_title_with_room_name", {
-            roomName: roomName,
-            clientShortname: mozL10n.get("clientShortname2")
-          }));
-        } else {
-          this.setTitle(mozL10n.get("clientShortname2"));
-        }
+        this.setTitle(mozL10n.get("standalone_title_with_room_name", {
+          roomName: roomName,
+          clientShortname: mozL10n.get("clientShortname2")
+        }));
       }
 
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({ publishVideo: true })
         }));
       }
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -738,16 +738,78 @@ describe("loop.panel", function() {
 
         roomEntry.setProps({ room: updatedRoom });
 
         expect(
           roomEntry.getDOMNode().textContent)
         .eql("New room name");
       });
     });
+
+    describe("Room name priority", function() {
+      var roomEntry;
+      beforeEach(function() {
+        roomEntry = mountRoomEntry({
+          dispatcher: dispatcher,
+          room: new loop.store.Room(roomData)
+        });
+      });
+
+      function setDecryptedContext(newDecryptedContext) {
+        return new loop.store.Room(_.extend({}, roomData, {
+          decryptedContext: newDecryptedContext,
+          ctime: new Date().getTime()
+        }));
+      }
+
+      it("should use room name by default", function() {
+        var updatedRoom = setDecryptedContext({
+          roomName: "Room name",
+          urls: [
+            {
+              description: "Website title",
+              location: "https://fakeurl.com"
+            }
+          ]
+        });
+
+        roomEntry.setProps({ room: updatedRoom });
+
+        expect(roomEntry.getDOMNode().textContent).eql("Room name");
+      });
+
+      it("should use context title when there's no room title", function() {
+        var updatedRoom = setDecryptedContext({
+          urls: [
+            {
+              description: "Website title",
+              location: "https://fakeurl.com"
+            }
+          ]
+        });
+
+        roomEntry.setProps({ room: updatedRoom });
+
+        expect(roomEntry.getDOMNode().textContent).eql("Website title");
+      });
+
+      it("should use website url when there's no room title nor website", function() {
+        var updatedRoom = setDecryptedContext({
+          urls: [
+            {
+              location: "https://fakeurl.com"
+            }
+          ]
+        });
+
+        roomEntry.setProps({ room: updatedRoom });
+
+        expect(roomEntry.getDOMNode().textContent).eql("https://fakeurl.com");
+      });
+    });
   });
 
   describe("loop.panel.RoomList", function() {
     var roomStore, dispatcher, fakeEmail, dispatch, roomData;
 
     beforeEach(function() {
       fakeEmail = "fakeEmail@example.com";
       dispatcher = new loop.Dispatcher();
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -644,16 +644,58 @@ describe("loop.roomViews", function() {
           videoMuted: false
         });
 
         view = mountTestComponent();
 
         expect(view.getDOMNode().querySelector(".local video")).not.eql(null);
       });
 
+      describe("Room name priority", function() {
+        var roomEntry;
+        beforeEach(function() {
+          activeRoomStore.setStoreState({
+            participants: [{}],
+            roomState: ROOM_STATES.JOINED,
+            roomName: "fakeName",
+            roomContextUrls: [
+              {
+                description: "Website title",
+                location: "https://fakeurl.com"
+              }
+            ]
+          });
+        });
+
+        it("should use room name by default", function() {
+          view = mountTestComponent();
+          expect(fakeWindow.document.title).to.equal("fakeName");
+        });
+
+        it("should use context title when there's no room title", function() {
+          activeRoomStore.setStoreState({ roomName: null });
+
+          view = mountTestComponent();
+          expect(fakeWindow.document.title).to.equal("Website title");
+        });
+
+        it("should use website url when there's no room title nor website", function() {
+          activeRoomStore.setStoreState({
+            roomName: null,
+            roomContextUrls: [
+                {
+                  location: "https://fakeurl.com"
+                }
+              ]
+          });
+          view = mountTestComponent();
+          expect(fakeWindow.document.title).to.equal("https://fakeurl.com");
+        });
+      });
+
     });
 
     describe("Edit Context", function() {
       it("should show the form when the edit button is clicked", function() {
         view = mountTestComponent();
         var node = view.getDOMNode();
 
         expect(node.querySelector(".room-context")).to.eql(null);
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -135,82 +135,141 @@ describe("loop.standaloneRoomViews", fun
         React.createElement(
           loop.standaloneRoomViews.StandaloneHandleUserAgentView, {
             dispatcher: dispatcher
           }));
     }
 
     it("should display a join room button if the state is not ROOM_JOINED", function() {
       activeRoomStore.setStoreState({
-        roomState: ROOM_STATES.READY
+        roomState: ROOM_STATES.READY,
+        roomName: "fakeName"
       });
 
       view = mountTestComponent();
       var button = view.getDOMNode().querySelector(".info-panel > button");
 
       expect(button.textContent).eql("rooms_room_join_label");
     });
 
     it("should dispatch a JoinRoom action when the join room button is clicked", function() {
       activeRoomStore.setStoreState({
-        roomState: ROOM_STATES.READY
+        roomState: ROOM_STATES.READY,
+        roomName: "fakeName"
       });
 
       view = mountTestComponent();
       var button = view.getDOMNode().querySelector(".info-panel > button");
 
       TestUtils.Simulate.click(button);
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.JoinRoom());
     });
 
     it("should display a enjoy your conversation button if the state is ROOM_JOINED", function() {
       activeRoomStore.setStoreState({
-        roomState: ROOM_STATES.JOINED
+        roomState: ROOM_STATES.JOINED,
+        roomName: "fakeName"
       });
 
       view = mountTestComponent();
       var button = view.getDOMNode().querySelector(".info-panel > button");
 
       expect(button.textContent).eql("rooms_room_joined_own_conversation_label");
     });
 
     it("should disable the enjoy your conversation button if the state is ROOM_JOINED", function() {
       activeRoomStore.setStoreState({
-        roomState: ROOM_STATES.JOINED
+        roomState: ROOM_STATES.JOINED,
+        roomName: "fakeName"
       });
 
       view = mountTestComponent();
       var button = view.getDOMNode().querySelector(".info-panel > button");
 
       expect(button.classList.contains("disabled")).eql(true);
     });
 
     it("should not display a join button if there is a failure reason", function() {
       activeRoomStore.setStoreState({
-        failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
+        failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN,
+        roomName: "fakeName"
       });
 
       view = mountTestComponent();
       var button = view.getDOMNode().querySelector(".info-panel > button");
 
       expect(button).eql(null);
     });
 
     it("should display a room already joined message if opening failed", function() {
       activeRoomStore.setStoreState({
-        failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
+        failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN,
+        roomName: "fakeName"
       });
 
       view = mountTestComponent();
       var text = view.getDOMNode().querySelector(".failure");
 
       expect(text.textContent).eql("rooms_already_joined");
     });
+
+    describe("Room name priority", function() {
+      it("should use room name", function() {
+        activeRoomStore.setStoreState({
+          roomState: ROOM_STATES.JOINED,
+          roomName: "fakeName"
+        });
+
+        view = mountTestComponent();
+        var text = view.getDOMNode().querySelector(".roomName");
+
+        expect(
+          text.textContent)
+        .eql("fakeName");
+      });
+
+      it("should use context title when there's no room title", function() {
+        activeRoomStore.setStoreState({
+          roomState: ROOM_STATES.JOINED,
+          roomContextUrls: [
+            {
+              description: "Website title",
+              location: "https://fakeurl.com"
+            }
+          ]
+        });
+
+        view = mountTestComponent();
+        var text = view.getDOMNode().querySelector(".roomName");
+
+        expect(
+          text.textContent)
+        .eql("Website title");
+      });
+
+      it("should use website url when there's no room title nor website", function() {
+        activeRoomStore.setStoreState({
+          roomState: ROOM_STATES.JOINED,
+          roomContextUrls: [
+            {
+              location: "https://fakeurl.com"
+            }
+          ]
+        });
+
+        view = mountTestComponent();
+        var text = view.getDOMNode().querySelector(".roomName");
+
+        expect(
+          text.textContent)
+        .eql("https://fakeurl.com");
+      });
+    });
   });
 
   describe("StandaloneRoomHeader", function() {
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.standaloneRoomViews.StandaloneOverlayWrapper, {
             dispatcher: dispatcher
@@ -236,17 +295,20 @@ describe("loop.standaloneRoomViews", fun
         dispatcher: dispatcher
       }, extraProps);
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.standaloneRoomViews.StandaloneRoomFailureView, props));
     }
 
     beforeEach(function() {
-      activeRoomStore.setStoreState({ roomState: ROOM_STATES.FAILED });
+      activeRoomStore.setStoreState({
+        roomState: ROOM_STATES.FAILED,
+        roomName: "fakeName"
+      });
     });
 
     it("should display a status error message if not reason is supplied", function() {
       view = mountTestComponent();
 
       expect(view.getDOMNode().querySelector(".failed-room-message").textContent)
         .eql("status_error");
     });
@@ -360,32 +422,27 @@ describe("loop.standaloneRoomViews", fun
 
     function expectActionDispatched() {
       sinon.assert.calledOnce(dispatch);
       sinon.assert.calledWithExactly(dispatch,
         sinon.match.instanceOf(sharedActions.SetupStreamElements));
     }
 
     describe("#componentWillUpdate", function() {
+      beforeEach(function() {
+        activeRoomStore.setStoreState({ roomName: "fakeName" });
+      });
       it("should set document.title to roomName and brand name when the READY state is dispatched", function() {
-        activeRoomStore.setStoreState({ roomName: "fakeName", roomState: ROOM_STATES.INIT });
+        activeRoomStore.setStoreState({ roomState: ROOM_STATES.INIT });
         view = mountTestComponent();
         activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
 
         expect(fakeWindow.document.title).to.equal("fakeName — clientShortname2");
       });
 
-      it("should set document.title brand name when there is no context available", function() {
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.INIT });
-        view = mountTestComponent();
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
-
-        expect(fakeWindow.document.title).to.equal("clientShortname2");
-      });
-
       it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state " +
         "is entered", function() {
           activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
           view = mountTestComponent();
 
           activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
 
           expectActionDispatched(view);
@@ -451,17 +508,17 @@ describe("loop.standaloneRoomViews", fun
         });
     });
 
     describe("#componentWillReceiveProps", function() {
       beforeEach(function() {
         view = mountTestComponent();
 
         // Pretend the user waited a little bit
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.JOINING });
+        activeRoomStore.setStoreState({ roomState: ROOM_STATES.JOINING, roomName: "fakeName" });
         clock.tick(loop.standaloneRoomViews.StandaloneRoomInfoArea.RENDER_WAITING_DELAY - 1);
       });
 
       describe("Support multiple joins", function() {
         it("should send the first `TileShown` after waiting in JOINING state",
           function() {
             clock.tick(1);
 
@@ -536,17 +593,17 @@ describe("loop.standaloneRoomViews", fun
           enabled: true
         }));
       });
     });
 
     describe("#render", function() {
       beforeEach(function() {
         view = mountTestComponent();
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.JOINING });
+        activeRoomStore.setStoreState({ roomState: ROOM_STATES.JOINING, roomName: "fakeName" });
       });
 
       describe("Empty room message", function() {
         it("should not display an message immediately in the JOINED state",
           function() {
             activeRoomStore.setStoreState({ roomState: ROOM_STATES.JOINED });
 
             expect(view.getDOMNode().querySelector(".empty-room-message"))
@@ -971,16 +1028,20 @@ describe("loop.standaloneRoomViews", fun
       return TestUtils.renderIntoDocument(
         React.createElement(
           loop.standaloneRoomViews.StandaloneRoomControllerView, {
         dispatcher: dispatcher,
         isFirefox: true
       }));
     }
 
+    beforeEach(function() {
+      activeRoomStore.setStoreState({ roomName: "fakeName" });
+    });
+
     it("should not display anything if it is not known if Firefox can handle the room", function() {
       activeRoomStore.setStoreState({
         userAgentHandlesRoom: undefined
       });
 
       view = mountTestComponent();
 
       expect(view.getDOMNode()).eql(null);
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1327,18 +1327,73 @@
             )
           )
 
         )
       );
     }
   });
 
+  var Failure = React.createClass({displayName: "Failure",
+    propTypes: {
+      errorDetected: React.PropTypes.bool.isRequired,
+      errorLine1: React.PropTypes.string,
+      errorLine2: React.PropTypes.string,
+      summary: React.PropTypes.string.isRequired
+    },
+
+    render: function() {
+      // if no errors, return blank
+      return !this.props.errorDetected ? null :
+      (React.createElement("li", {className: "test fail"}, 
+          React.createElement("h2", null, 
+            this.props.summary
+          ), 
+          React.createElement("pre", {className: "error"}, 
+            this.props.errorLine1 +
+             this.props.errorLine2 ? "\n" + this.props.errorLine2 : ""
+          )
+        )
+      );
+    }
+  });
+
+  var Result = React.createClass({displayName: "Result",
+    propTypes: {
+      error: React.PropTypes.object,
+      warnings: React.PropTypes.array
+    },
+
+    render: function() {
+      var warningsDetected = this.props.warnings.length !== 0;
+      var totalFailures = warningsDetected + !!this.props.error;
+
+      return (
+        React.createElement("div", {className: "error-summary"}, 
+          React.createElement("div", {className: "failures"}, 
+            React.createElement("a", null, "failures: "), 
+            React.createElement("em", null, totalFailures)
+          ), 
+          React.createElement("ul", null, 
+            React.createElement(Failure, {errorDetected: warningsDetected, 
+                     errorLine1: "Got: " + this.props.warnings.length, 
+                     summary: "Unexpected warnings detected rendering UI-Showcase"}), 
+            React.createElement(Failure, {errorDetected: !!this.props.error, 
+                     errorLine1: this.props.error, 
+                     errorLine2: this.props.error ? this.props.error.stack : null, 
+                     summary: "Errors rendering UI-Showcase"})
+          ), 
+          React.createElement("p", {id: "complete"}, "Completed")
+        )
+      );
+    }
+  });
+
   window.addEventListener("DOMContentLoaded", function() {
-    var uncaughtError;
+    var uncaughtError = null;
     var consoleWarn = console.warn;
     var caughtWarnings = [];
     console.warn = function() {
       var args = Array.slice(arguments);
       caughtWarnings.push(args);
       consoleWarn.apply(console, args);
     };
 
@@ -1357,60 +1412,15 @@
     setTimeout(function waitForQueuedFrames() {
       if (window.queuedFrames.length !== 0) {
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
-      // This simulates the mocha layout for errors which means we can run
-      // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 0;
-      var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
-      var resultsElement = document.querySelector("#results");
-      var divFailuresNode = document.createElement("div");
-      var pCompleteNode = document.createElement("p");
-      var emNode = document.createElement("em");
-
-      if (uncaughtError || warningsMismatch) {
-        var liTestFail = document.createElement("li");
-        var h2Node = document.createElement("h2");
-        var preErrorNode = document.createElement("pre");
-
-        divFailuresNode.className = "failures";
-        emNode.innerHTML = ((uncaughtError && warningsMismatch) ? 2 : 1).toString();
-        divFailuresNode.appendChild(emNode);
-        resultsElement.appendChild(divFailuresNode);
-
-        if (warningsMismatch) {
-          liTestFail.className = "test";
-          liTestFail.className += " fail";
-          h2Node.innerHTML = "Unexpected number of warnings detected in UI-Showcase";
-          preErrorNode.className = "error";
-          preErrorNode.innerHTML = "Got: " + caughtWarnings.length + "\n" + "Expected: " + expectedWarningsCount;
-          liTestFail.appendChild(h2Node);
-          liTestFail.appendChild(preErrorNode);
-          resultsElement.appendChild(liTestFail);
-        }
-        if (uncaughtError) {
-          liTestFail.className = "test";
-          liTestFail.className += " fail";
-          h2Node.innerHTML = "Errors rendering UI-Showcase";
-          preErrorNode.className = "error";
-          preErrorNode.innerHTML = uncaughtError + "\n" + uncaughtError.stack;
-          liTestFail.appendChild(h2Node);
-          liTestFail.appendChild(preErrorNode);
-          resultsElement.appendChild(liTestFail);
-        }
-      } else {
-        divFailuresNode.className = "failures";
-        emNode.innerHTML = "0";
-        divFailuresNode.appendChild(emNode);
-        resultsElement.appendChild(divFailuresNode);
-      }
-      pCompleteNode.id = "complete";
-      pCompleteNode.innerHTML = "Completed";
-      resultsElement.appendChild(pCompleteNode);
+      React.render(React.createElement(Result, {error: uncaughtError, 
+                           warnings: caughtWarnings}),
+                   document.querySelector("#results"));
     }, 1000);
   });
 
 })();
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1327,18 +1327,73 @@
             </FramedExample>
           </Section>
 
         </ShowCase>
       );
     }
   });
 
+  var Failure = React.createClass({
+    propTypes: {
+      errorDetected: React.PropTypes.bool.isRequired,
+      errorLine1: React.PropTypes.string,
+      errorLine2: React.PropTypes.string,
+      summary: React.PropTypes.string.isRequired
+    },
+
+    render: function() {
+      // if no errors, return blank
+      return !this.props.errorDetected ? null :
+      (<li className = "test fail">
+          <h2>
+            {this.props.summary}
+          </h2>
+          <pre className="error">
+            {this.props.errorLine1 +
+             this.props.errorLine2 ? "\n" + this.props.errorLine2 : ""}
+          </pre>
+        </li>
+      );
+    }
+  });
+
+  var Result = React.createClass({
+    propTypes: {
+      error: React.PropTypes.object,
+      warnings: React.PropTypes.array
+    },
+
+    render: function() {
+      var warningsDetected = this.props.warnings.length !== 0;
+      var totalFailures = warningsDetected + !!this.props.error;
+
+      return (
+        <div className = "error-summary">
+          <div className = "failures">
+            <a>failures: </a>
+            <em>{totalFailures}</em>
+          </div>
+          <ul>
+            <Failure errorDetected={warningsDetected}
+                     errorLine1={"Got: " + this.props.warnings.length}
+                     summary="Unexpected warnings detected rendering UI-Showcase" />
+            <Failure errorDetected={!!this.props.error}
+                     errorLine1={this.props.error}
+                     errorLine2={this.props.error ? this.props.error.stack : null}
+                     summary="Errors rendering UI-Showcase" />
+          </ul>
+          <p id="complete">Completed</p>
+        </div>
+      );
+    }
+  });
+
   window.addEventListener("DOMContentLoaded", function() {
-    var uncaughtError;
+    var uncaughtError = null;
     var consoleWarn = console.warn;
     var caughtWarnings = [];
     console.warn = function() {
       var args = Array.slice(arguments);
       caughtWarnings.push(args);
       consoleWarn.apply(console, args);
     };
 
@@ -1357,60 +1412,15 @@
     setTimeout(function waitForQueuedFrames() {
       if (window.queuedFrames.length !== 0) {
         setTimeout(waitForQueuedFrames, 500);
         return;
       }
       // Put the title back, in case views changed it.
       document.title = "Loop UI Components Showcase";
 
-      // This simulates the mocha layout for errors which means we can run
-      // this alongside our other unit tests but use the same harness.
-      var expectedWarningsCount = 0;
-      var warningsMismatch = caughtWarnings.length !== expectedWarningsCount;
-      var resultsElement = document.querySelector("#results");
-      var divFailuresNode = document.createElement("div");
-      var pCompleteNode = document.createElement("p");
-      var emNode = document.createElement("em");
-
-      if (uncaughtError || warningsMismatch) {
-        var liTestFail = document.createElement("li");
-        var h2Node = document.createElement("h2");
-        var preErrorNode = document.createElement("pre");
-
-        divFailuresNode.className = "failures";
-        emNode.innerHTML = ((uncaughtError && warningsMismatch) ? 2 : 1).toString();
-        divFailuresNode.appendChild(emNode);
-        resultsElement.appendChild(divFailuresNode);
-
-        if (warningsMismatch) {
-          liTestFail.className = "test";
-          liTestFail.className += " fail";
-          h2Node.innerHTML = "Unexpected number of warnings detected in UI-Showcase";
-          preErrorNode.className = "error";
-          preErrorNode.innerHTML = "Got: " + caughtWarnings.length + "\n" + "Expected: " + expectedWarningsCount;
-          liTestFail.appendChild(h2Node);
-          liTestFail.appendChild(preErrorNode);
-          resultsElement.appendChild(liTestFail);
-        }
-        if (uncaughtError) {
-          liTestFail.className = "test";
-          liTestFail.className += " fail";
-          h2Node.innerHTML = "Errors rendering UI-Showcase";
-          preErrorNode.className = "error";
-          preErrorNode.innerHTML = uncaughtError + "\n" + uncaughtError.stack;
-          liTestFail.appendChild(h2Node);
-          liTestFail.appendChild(preErrorNode);
-          resultsElement.appendChild(liTestFail);
-        }
-      } else {
-        divFailuresNode.className = "failures";
-        emNode.innerHTML = "0";
-        divFailuresNode.appendChild(emNode);
-        resultsElement.appendChild(divFailuresNode);
-      }
-      pCompleteNode.id = "complete";
-      pCompleteNode.innerHTML = "Completed";
-      resultsElement.appendChild(pCompleteNode);
+      React.render(<Result error={uncaughtError}
+                           warnings={caughtWarnings} />,
+                   document.querySelector("#results"));
     }, 1000);
   });
 
 })();
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -51,16 +51,18 @@ const MAX_SUGGESTIONS = 6;
  *   ManageEngines
  *     Opens the search engine management window.
  *     data: null
  *   RemoveFormHistoryEntry
  *     Removes an entry from the search form history.
  *     data: the entry, a string
  *   Search
  *     Performs a search.
+ *     Any GetSuggestions messages in the queue from the same target will be
+ *     cancelled.
  *     data: { engineName, searchString, healthReportKey, searchPurpose }
  *   SetCurrentEngine
  *     Sets the current engine.
  *     data: the name of the engine
  *   SpeculativeConnect
  *     Speculatively connects to an engine.
  *     data: the name of the engine
  *
@@ -76,16 +78,20 @@ const MAX_SUGGESTIONS = 6;
  *     Sent in reply to GetState.
  *     data: see _currentStateObj
  *   Strings
  *     Sent in reply to GetStrings
  *     data: Object containing string names and values for the current locale.
  *   Suggestions
  *     Sent in reply to GetSuggestions.
  *     data: see _onMessageGetSuggestions
+ *   SuggestionsCancelled
+ *     Sent in reply to GetSuggestions when pending GetSuggestions events are
+ *     cancelled.
+ *     data: null
  */
 
 this.ContentSearch = {
 
   // Inbound events are queued and processed in FIFO order instead of handling
   // them immediately, which would result in non-FIFO responses due to the
   // asynchrononicity added by converting image data URIs to ArrayBuffers.
   _eventQueue: [],
@@ -93,16 +99,20 @@ this.ContentSearch = {
 
   // This is used to handle search suggestions.  It maps xul:browsers to objects
   // { controller, previousFormHistoryResult }.  See _onMessageGetSuggestions.
   _suggestionMap: new WeakMap(),
 
   // Resolved when we finish shutting down.
   _destroyedPromise: null,
 
+  // The current controller and browser in _onMessageGetSuggestions.  Allows
+  // fetch cancellation from _cancelSuggestions.
+  _currentSuggestion: null,
+
   init: function () {
     Cc["@mozilla.org/globalmessagemanager;1"].
       getService(Ci.nsIMessageListenerManager).
       addMessageListener(INBOUND_MESSAGE, this);
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
     Services.obs.addObserver(this, "shutdown-leaks-before-check", false);
     Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false);
     this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
@@ -160,16 +170,22 @@ this.ContentSearch = {
         this._suggestionMap.set(event.detail, browserData);
       }
       msg.target.removeEventListener("SwapDocShells", msg, true);
       msg.target = event.detail;
       msg.target.addEventListener("SwapDocShells", msg, true);
     };
     msg.target.addEventListener("SwapDocShells", msg, true);
 
+    // Search requests cause cancellation of all Suggestion requests from the
+    // same browser.
+    if (msg.data.type == "Search") {
+      this._cancelSuggestions(msg);
+    }
+
     this._eventQueue.push({
       type: "Message",
       data: msg,
     });
     this._processEventQueue();
   },
 
   observe: function (subj, topic, data) {
@@ -203,16 +219,37 @@ this.ContentSearch = {
         Cu.reportError(err);
       } finally {
         this._currentEventPromise = null;
         this._processEventQueue();
       }
     }.bind(this));
   },
 
+  _cancelSuggestions: function (msg) {
+    let cancelled = false;
+    // cancel active suggestion request
+    if (this._currentSuggestion && this._currentSuggestion.target == msg.target) {
+      this._currentSuggestion.controller.stop();
+      cancelled = true;
+    }
+    // cancel queued suggestion requests
+    for (let i = 0; i < this._eventQueue.length; i++) {
+      let m = this._eventQueue[i].data;
+      if (msg.target == m.target && m.data.type == "GetSuggestions") {
+        this._eventQueue.splice(i, 1);
+        cancelled = true;
+        i--;
+      }
+    }
+    if (cancelled) {
+      this._reply(msg, "SuggestionsCancelled");
+    }
+  },
+
   _onMessage: Task.async(function* (msg) {
     let methodName = "_onMessage" + msg.data.type;
     if (methodName in this) {
       yield this._initService();
       yield this[methodName](msg, msg.data.data);
       msg.target.removeEventListener("SwapDocShells", msg, true);
     }
   }),
@@ -297,17 +334,24 @@ this.ContentSearch = {
     let { controller } = browserData;
     let ok = SearchSuggestionController.engineOffersSuggestions(engine);
     controller.maxLocalResults = ok ? MAX_LOCAL_SUGGESTIONS : MAX_SUGGESTIONS;
     controller.maxRemoteResults = ok ? MAX_SUGGESTIONS : 0;
     controller.remoteTimeout = data.remoteTimeout || undefined;
     let priv = PrivateBrowsingUtils.isBrowserPrivate(msg.target);
     // fetch() rejects its promise if there's a pending request, but since we
     // process our event queue serially, there's never a pending request.
+    this._currentSuggestion = { controller: controller, target: msg.target };
     let suggestions = yield controller.fetch(data.searchString, priv, engine);
+    this._currentSuggestion = null;
+
+    // suggestions will be null if the request was cancelled
+    if (!suggestions) {
+      return;
+    }
 
     // Keep the form history result so RemoveFormHistoryEntry can remove entries
     // from it.  Keeping only one result isn't foolproof because the client may
     // try to remove an entry from one set of suggestions after it has requested
     // more but before it's received them.  In that case, the entry may not
     // appear in the new suggestions.  But that should happen rarely.
     browserData.previousFormHistoryResult = suggestions.formHistoryResult;
 
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1942,11 +1942,12 @@ toolbarbutton.chevron > .toolbarbutton-i
   visibility: visible;
   /* override toolkit/themes/linux/global/menu.css */
   -moz-padding-end: 0 !important;
   -moz-margin-end: 0 !important;
 }
 
 .browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
+  overflow: hidden;
 }
 
 %include ../shared/usercontext/usercontext.inc.css
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3616,11 +3616,12 @@ notification[value="loop-sharing-notific
 
 #context-navigation > .menuitem-iconic {
   padding-left: 0;
   padding-right: 0;
 }
 
 .browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
+  overflow: hidden;
 }
 
 %include ../shared/usercontext/usercontext.inc.css
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2802,11 +2802,12 @@ notification[value="loop-sharing-notific
 
 
 @media not all and (-moz-os-version: windows-xp) {
 %include browser-aero.css
 }
 
 .browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
+  overflow: hidden;
 }
 
 %include ../shared/usercontext/usercontext.inc.css
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -332,16 +332,29 @@ if test -n "$MOZ_NATIVE_DEVICES" ; then
     MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms)
     MOZ_ANDROID_AAR(play-services-cast, 8.1.0, google, com/google/android/gms)
     MOZ_ANDROID_AAR(mediarouter-v7, 23.0.1, android, com/android/support, REQUIRED_INTERNAL_IMPL)
 fi
 
 ])
 
+AC_DEFUN([MOZ_ANDROID_GOOGLE_CLOUD_MESSAGING],
+[
+
+if test -n "$MOZ_ANDROID_GCM" ; then
+    AC_SUBST(MOZ_ANDROID_GCM)
+
+    MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms)
+    MOZ_ANDROID_AAR(play-services-gcm, 8.1.0, google, com/google/android/gms)
+fi
+
+])
+
 dnl Configure an Android SDK.
 dnl Arg 1: target SDK version, like 22.
 dnl Arg 2: build tools version, like 22.0.1.
 AC_DEFUN([MOZ_ANDROID_SDK],
 [
 
 MOZ_ARG_WITH_STRING(android-sdk,
 [  --with-android-sdk=DIR
--- a/build/dumbmake-dependencies
+++ b/build/dumbmake-dependencies
@@ -43,28 +43,29 @@ toolkit/library
   image/build
     image
   intl/build
     intl
   media
   profile
   services
   startupcache
+  devtools/server
+  devtools/shared
 browser/app
   browser/base
   browser/components
   devtools/client
   browser/locales
   browser/modules
   browser/themes
   toolkit
   toolkit/components
   toolkit/components/jsdownloads
   toolkit/content
   toolkit/crashreporter
-  devtools/shared
   toolkit/forgetaboutsite
   toolkit/identity
   toolkit/modules
   toolkit/mozapps/extensions
   toolkit/profile
   toolkit/themes
   toolkit/webapps
--- a/config/check_macroassembler_style.py
+++ b/config/check_macroassembler_style.py
@@ -26,17 +26,17 @@ import difflib
 import os
 import re
 import subprocess
 import sys
 from check_utils import get_all_toplevel_filenames
 
 architecture_independent = set([ 'generic' ])
 all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64', 'mips32' ])
-all_shared_architecture_names = set([ 'x86_shared', 'arm', 'arm64', 'mips32' ])
+all_shared_architecture_names = set([ 'x86_shared', 'mips_shared', 'arm', 'arm64' ])
 
 reBeforeArg = "(?<=[(,\s])"
 reArgType = "(?P<type>[\w\s:*&]+)"
 reArgName = "(?P<name>\s\w+)"
 reArgDefault = "(?P<default>(?:\s=[^,)]+)?)"
 reAfterArg = "(?=[,)])"
 reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
 
--- a/configure.in
+++ b/configure.in
@@ -3750,16 +3750,17 @@ BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
 MOZ_WEBSMS_BACKEND=
 MOZ_ANDROID_BEAM=
 MOZ_LOCALE_SWITCHER=
 MOZ_ANDROID_READING_LIST_SERVICE=
 MOZ_ANDROID_SEARCH_ACTIVITY=
 MOZ_ANDROID_DOWNLOADS_INTEGRATION=
+MOZ_ANDROID_GCM=
 MOZ_ANDROID_MLS_STUMBLER=
 MOZ_ANDROID_SHARE_OVERLAY=
 MOZ_EXCLUDE_HYPHENATION_DICTIONARIES=
 MOZ_INSTALL_TRACKING=
 MOZ_SWITCHBOARD=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
 MOZ_SIMPLEPUSH=
@@ -3906,16 +3907,24 @@ AC_SUBST(MOZ_BING_API_KEY)
 
 # Allow specifying an Adjust SDK key file that contains the app token used for
 # Adjust SDK requests.
 MOZ_ARG_WITH_STRING(adjust-sdk-keyfile,
 [  --with-adjust-sdk-keyfile=file   Use the app token contained in the given keyfile for Adjust SDK requests],
   MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN=`cat $withval`)
 AC_SUBST(MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN)
 
+# Allow specifying a GCM sender ID key file that contains the sender ID used for
+# GCM requests.  Note that GCM sender IDs are not sensitive: see, for example,
+# http://stackoverflow.com/a/18216063.
+MOZ_ARG_WITH_STRING(gcm-senderid-keyfile,
+[  --with-gcm-senderid-keyfile=file GCM sender ID for GCM requests],
+  MOZ_ANDROID_GCM_SENDERID=`cat $withval`)
+AC_SUBST(MOZ_ANDROID_GCM_SENDERID)
+
 # Whether to include optional-but-large font files in the final APK.
 # We want this in mobile/android/confvars.sh, so it goes early.
 MOZ_ARG_DISABLE_BOOL(android-include-fonts,
 [  --disable-android-include-fonts
                           Disable the inclusion of fonts into the final APK],
     MOZ_ANDROID_EXCLUDE_FONTS=1)
 
 if test -n "$MOZ_ANDROID_EXCLUDE_FONTS"; then
@@ -4575,16 +4584,17 @@ AC_SUBST(MOZ_DISTRIBUTION_ID)
 
 
 dnl ========================================================
 dnl Google Play Services, placed here so it can depend on
 dnl values set by configure.sh above.
 dnl ========================================================
 
 MOZ_ANDROID_GOOGLE_PLAY_SERVICES
+MOZ_ANDROID_GOOGLE_CLOUD_MESSAGING
 
 
 dnl ========================================================
 dnl = Pango
 dnl ========================================================
 if test "$MOZ_ENABLE_GTK" -o "$MOZ_ENABLE_QT"
 then
     PKG_CHECK_MODULES(_PANGOCHK, pango >= $PANGO_VERSION)
@@ -4882,16 +4892,23 @@ fi
 dnl ========================================================
 dnl = Include Switchboard A/B framework on Android
 dnl ========================================================
 if test -n "$MOZ_SWITCHBOARD"; then
     AC_DEFINE(MOZ_SWITCHBOARD)
 fi
 
 dnl ========================================================
+dnl = Enable GCM on Android.
+dnl ========================================================
+if test -n "$MOZ_ANDROID_GCM"; then
+    AC_DEFINE(MOZ_ANDROID_GCM)
+fi
+
+dnl ========================================================
 dnl = Enable IPDL's "expensive" unit tests
 dnl ========================================================
 MOZ_IPDL_TESTS=
 
 MOZ_ARG_ENABLE_BOOL(ipdl-tests,
 [  --enable-ipdl-tests     Enable expensive IPDL tests],
     MOZ_IPDL_TESTS=1,
     MOZ_IPDL_TESTS=)
@@ -8587,16 +8604,17 @@ AC_SUBST(MOZ_DIRECTX_SDK_PATH)
 AC_SUBST(MOZ_D3DCOMPILER_XP_DLL)
 AC_SUBST(MOZ_D3DCOMPILER_XP_CAB)
 
 AC_SUBST(MOZ_ANDROID_HISTORY)
 AC_SUBST(MOZ_WEBSMS_BACKEND)
 AC_SUBST(MOZ_ANDROID_BEAM)
 AC_SUBST(MOZ_LOCALE_SWITCHER)
 AC_SUBST(MOZ_DISABLE_GECKOVIEW)
+AC_SUBST(MOZ_ANDROID_GCM)
 AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
 AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
 AC_SUBST(MOZ_ANDROID_SHARE_OVERLAY)
 AC_SUBST(MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES)
 AC_SUBST(MOZ_ANDROID_TAB_QUEUE)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js
@@ -20,18 +20,25 @@ function test() {
     gFiltering = gDebugger.DebuggerView.Filtering;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceShown(gPanel, ".html").then(performTest);
   });
 }
 
 function performTest() {
+  // Make sure that the search box becomes focused when pressing ctrl+f - Bug 1211038
+  gEditor.focus();
+  synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey"));
+  let focusedEl = Services.focus.focusedElement;
+  focusedEl = focusedEl.ownerDocument.getBindingParent(focusedEl) || focusedEl;
+  is(focusedEl, gDebugger.document.getElementById("searchbox"), "Searchbox is focused");
+
   setText(gSearchBox, "#html");
-  
+
   EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger);
   is(gFiltering.searchData.toSource(), '["#", ["", "html"]]',
     "The searchbox data wasn't parsed correctly.");
   ok(isCaretPos(gPanel, 35, 7),
     "The editor didn't jump to the correct line.");
 
   EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger);
   is(gFiltering.searchData.toSource(), '["#", ["", "html"]]',
@@ -39,17 +46,17 @@ function performTest() {
   ok(isCaretPos(gPanel, 5, 6),
     "The editor didn't jump to the correct line.");
 
   EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger);
   is(gFiltering.searchData.toSource(), '["#", ["", "html"]]',
     "The searchbox data wasn't parsed correctly.");
   ok(isCaretPos(gPanel, 3, 15),
     "The editor didn't jump to the correct line.");
-  
+
   setText(gSearchBox, ":12");
   is(gFiltering.searchData.toSource(), '[":", ["", 12]]',
     "The searchbox data wasn't parsed correctly.");
   ok(isCaretPos(gPanel, 12),
     "The editor didn't jump to the correct line.");
 
   EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger);
   is(gFiltering.searchData.toSource(), '[":", ["", 13]]',
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -1210,8 +1210,35 @@ function getSplitConsole(toolbox, win) {
   return new Promise(resolve => {
     toolbox.getPanelWhenReady("webconsole").then(() => {
       ok(toolbox.splitConsole, "Split console is shown.");
       let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
       resolve(jsterm);
     });
   });
 }
+
+// This can be removed once debugger uses shared-head.js (bug 1181838)
+function synthesizeKeyFromKeyTag(key) {
+  is(key && key.tagName, "key", "Successfully retrieved the <key> node");
+
+  let modifiersAttr = key.getAttribute("modifiers");
+
+  let name = null;
+
+  if (key.getAttribute("keycode"))
+    name = key.getAttribute("keycode");
+  else if (key.getAttribute("key"))
+    name = key.getAttribute("key");
+
+  isnot(name, null, "Successfully retrieved keycode/key");
+
+  let modifiers = {
+    shiftKey: !!modifiersAttr.match("shift"),
+    ctrlKey: !!modifiersAttr.match("control"),
+    altKey: !!modifiersAttr.match("alt"),
+    metaKey: !!modifiersAttr.match("meta"),
+    accelKey: !!modifiersAttr.match("accel")
+  };
+
+  info("Synthesizing key " + name + " " + JSON.stringify(modifiers));
+  EventUtils.synthesizeKey(name, modifiers);
+}
--- a/devtools/client/memory/actions/breakdown.js
+++ b/devtools/client/memory/actions/breakdown.js
@@ -1,43 +1,33 @@
 /* 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";
 
-// @TODO 1215606
-// Use this assert instead of utils when fixed.
-// const { assert } = require("devtools/shared/DevToolsUtils");
-const { breakdownEquals, createSnapshot, assert } = require("../utils");
+const { assert } = require("devtools/shared/DevToolsUtils");
+const { breakdownEquals, createSnapshot } = require("../utils");
 const { actions, snapshotState: states } = require("../constants");
-const { takeCensus } = require("./snapshot");
+const { refreshSelectedCensus } = require("./snapshot");
 
 const setBreakdownAndRefresh = exports.setBreakdownAndRefresh = function (heapWorker, breakdown) {
   return function *(dispatch, getState) {
-    // Clears out all stored census data and sets
-    // the breakdown
+    // Clears out all stored census data and sets the breakdown.
     dispatch(setBreakdown(breakdown));
-    let snapshot = getState().snapshots.find(s => s.selected);
-
-    // If selected snapshot does not have updated census if the breakdown
-    // changed, retake the census with new breakdown
-    if (snapshot && !breakdownEquals(snapshot.breakdown, breakdown)) {
-      yield dispatch(takeCensus(heapWorker, snapshot));
-    }
+    yield dispatch(refreshSelectedCensus(heapWorker));
   };
 };
 
 /**
  * Clears out all census data in the snapshots and sets
  * a new breakdown.
  *
  * @param {Breakdown} breakdown
  */
 const setBreakdown = exports.setBreakdown = function (breakdown) {
-  // @TODO 1215606
   assert(typeof breakdown === "object" && breakdown.by,
     `Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(breakdown)}`);
 
   return {
     type: actions.SET_BREAKDOWN,
     breakdown,
   }
 };
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/actions/inverted.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { actions } = require("../constants");
+const { refreshSelectedCensus } = require("./snapshot");
+
+const toggleInverted = exports.toggleInverted = function () {
+  return { type: actions.TOGGLE_INVERTED };
+};
+
+exports.toggleInvertedAndRefresh = function (heapWorker) {
+  return function* (dispatch, getState) {
+    dispatch(toggleInverted());
+    yield dispatch(refreshSelectedCensus(heapWorker));
+  };
+};
--- a/devtools/client/memory/actions/moz.build
+++ b/devtools/client/memory/actions/moz.build
@@ -1,10 +1,11 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'allocations.js',
     'breakdown.js',
+    'inverted.js',
     'snapshot.js',
 )
--- a/devtools/client/memory/actions/snapshot.js
+++ b/devtools/client/memory/actions/snapshot.js
@@ -1,119 +1,163 @@
 /* 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";
 
-// @TODO 1215606
-// Use this assert instead of utils when fixed.
-// const { assert } = require("devtools/shared/DevToolsUtils");
-const { getSnapshot, breakdownEquals, createSnapshot, assert } = require("../utils");
+const { assert, reportException } = require("devtools/shared/DevToolsUtils");
+const { getSnapshot, breakdownEquals, createSnapshot } = require("../utils");
 const { actions, snapshotState: states } = require("../constants");
 
 /**
  * A series of actions are fired from this task to save, read and generate the initial
  * census from a snapshot.
  *
  * @param {MemoryFront}
  * @param {HeapAnalysesClient}
  * @param {Object}
  */
 const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function (front, heapWorker) {
   return function *(dispatch, getState) {
     let snapshot = yield dispatch(takeSnapshot(front));
+
     yield dispatch(readSnapshot(heapWorker, snapshot));
-    yield dispatch(takeCensus(heapWorker, snapshot));
+    if (snapshot.state === states.READ) {
+      yield dispatch(takeCensus(heapWorker, snapshot));
+    }
   };
 };
 
 /**
  * Selects a snapshot and if the snapshot's census is using a different
  * breakdown, take a new census.
  *
  * @param {HeapAnalysesClient}
  * @param {Snapshot}
  */
 const selectSnapshotAndRefresh = exports.selectSnapshotAndRefresh = function (heapWorker, snapshot) {
   return function *(dispatch, getState) {
     dispatch(selectSnapshot(snapshot));
-
-    // Attempt to take another census; if the snapshot already is using
-    // the correct breakdown, this will noop.
-    yield dispatch(takeCensus(heapWorker, snapshot));
+    yield dispatch(refreshSelectedCensus(heapWorker));
   };
 };
 
 /**
  * @param {MemoryFront}
  */
 const takeSnapshot = exports.takeSnapshot = function (front) {
   return function *(dispatch, getState) {
     let snapshot = createSnapshot();
     dispatch({ type: actions.TAKE_SNAPSHOT_START, snapshot });
     dispatch(selectSnapshot(snapshot));
 
-    let path = yield front.saveHeapSnapshot();
+    let path;
+    try {
+      path = yield front.saveHeapSnapshot();
+    } catch (error) {
+      reportException("takeSnapshot", error);
+      dispatch({ type: actions.SNAPSHOT_ERROR, snapshot, error });
+      return;
+    }
+
     dispatch({ type: actions.TAKE_SNAPSHOT_END, snapshot, path });
-
     return snapshot;
   };
 };
 
 /**
  * Reads a snapshot into memory; necessary to do before taking
  * a census on the snapshot. May only be called once per snapshot.
  *
  * @param {HeapAnalysesClient}
  * @param {Snapshot} snapshot,
  */
 const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, snapshot) {
   return function *(dispatch, getState) {
-    // @TODO 1215606
     assert(snapshot.state === states.SAVED,
-      "Should only read a snapshot once");
+      `Should only read a snapshot once. Found snapshot in state ${snapshot.state}`);
 
     dispatch({ type: actions.READ_SNAPSHOT_START, snapshot });
-    yield heapWorker.readHeapSnapshot(snapshot.path);
+    try {
+      yield heapWorker.readHeapSnapshot(snapshot.path);
+    } catch (error) {
+      reportException("readSnapshot", error);
+      dispatch({ type: actions.SNAPSHOT_ERROR, snapshot, error });
+      return;
+    }
+
     dispatch({ type: actions.READ_SNAPSHOT_END, snapshot });
   };
 };
 
 /**
  * @param {HeapAnalysesClient} heapWorker
  * @param {Snapshot} snapshot,
  *
  * @see {Snapshot} model defined in devtools/client/memory/models.js
  * @see `devtools/shared/heapsnapshot/HeapAnalysesClient.js`
  * @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
  */
 const takeCensus = exports.takeCensus = function (heapWorker, snapshot) {
   return function *(dispatch, getState) {
-    // @TODO 1215606
     assert([states.READ, states.SAVED_CENSUS].includes(snapshot.state),
-      "Can only take census of snapshots in READ or SAVED_CENSUS state");
+      `Can only take census of snapshots in READ or SAVED_CENSUS state, found ${snapshot.state}`);
 
     let census;
+    let inverted = getState().inverted;
     let breakdown = getState().breakdown;
 
-    // If breakdown hasn't changed, don't do anything
-    if (breakdownEquals(breakdown, snapshot.breakdown)) {
+    // If breakdown and inversion haven't changed, don't do anything.
+    if (inverted === snapshot.inverted && breakdownEquals(breakdown, snapshot.breakdown)) {
       return;
     }
 
     // Keep taking a census if the breakdown changes during. Recheck
     // that the breakdown used for the census is the same as
     // the state's breakdown.
     do {
+      inverted = getState().inverted;
       breakdown = getState().breakdown;
-      dispatch({ type: actions.TAKE_CENSUS_START, snapshot, breakdown });
-      census = yield heapWorker.takeCensus(snapshot.path, { breakdown }, { asTreeNode: true });
-    } while (!breakdownEquals(breakdown, getState().breakdown));
+      dispatch({ type: actions.TAKE_CENSUS_START, snapshot, inverted, breakdown });
+      let opts = inverted ? { asInvertedTreeNode: true } : { asTreeNode: true };
+
+      try {
+        census = yield heapWorker.takeCensus(snapshot.path, { breakdown }, opts);
+      } catch(error) {
+        reportException("takeCensus", error);
+        dispatch({ type: actions.SNAPSHOT_ERROR, snapshot, error });
+        return;
+      }
+    }
+    while (inverted !== getState().inverted ||
+           !breakdownEquals(breakdown, getState().breakdown));
+
+    dispatch({ type: actions.TAKE_CENSUS_END, snapshot, breakdown, inverted, census });
+  };
+};
 
-    dispatch({ type: actions.TAKE_CENSUS_END, snapshot, breakdown, census });
+/**
+ * Refresh the selected snapshot's census data, if need be (for example,
+ * breakdown configuration changed).
+ *
+ * @param {HeapAnalysesClient} heapWorker
+ */
+const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWorker) {
+  return function*(dispatch, getState) {
+    let snapshot = getState().snapshots.find(s => s.selected);
+
+    // Intermediate snapshot states will get handled by the task action that is
+    // orchestrating them. For example, if the snapshot's state is
+    // SAVING_CENSUS, then the takeCensus action will keep taking a census until
+    // the inverted property matches the inverted state. If the snapshot is
+    // still in the process of being saved or read, the takeSnapshotAndCensus
+    // task action will follow through and ensure that a census is taken.
+    if (snapshot && snapshot.state === states.SAVED_CENSUS) {
+      yield dispatch(takeCensus(heapWorker, snapshot));
+    }
   };
 };
 
 /**
  * @param {Snapshot}
  * @see {Snapshot} model defined in devtools/client/memory/models.js
  */
 const selectSnapshot = exports.selectSnapshot = function (snapshot) {
--- a/devtools/client/memory/app.js
+++ b/devtools/client/memory/app.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { toggleRecordingAllocationStacks } = require("./actions/allocations");
 const { setBreakdownAndRefresh } = require("./actions/breakdown");
+const { toggleInvertedAndRefresh } = require("./actions/inverted");
 const { selectSnapshotAndRefresh, takeSnapshotAndCensus } = require("./actions/snapshot");
 const { breakdownNameToSpec, getBreakdownDisplayData } = require("./utils");
 const Toolbar = createFactory(require("./components/toolbar"));
 const List = createFactory(require("./components/list"));
 const SnapshotListItem = createFactory(require("./components/snapshot-list-item"));
 const HeapView = createFactory(require("./components/heap"));
 const { app: appModel } = require("./models");
 
@@ -34,31 +35,35 @@ const App = createClass({
   render() {
     let {
       dispatch,
       snapshots,
       front,
       heapWorker,
       breakdown,
       allocations,
+      inverted
     } = this.props;
 
     let selectedSnapshot = snapshots.find(s => s.selected);
 
     return (
       dom.div({ id: "memory-tool" }, [
 
         Toolbar({
           breakdowns: getBreakdownDisplayData(),
           onTakeSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)),
           onBreakdownChange: breakdown =>
             dispatch(setBreakdownAndRefresh(heapWorker, breakdownNameToSpec(breakdown))),
           onToggleRecordAllocationStacks: () =>
             dispatch(toggleRecordingAllocationStacks(front)),
-          allocations
+          allocations,
+          inverted,
+          onToggleInverted: () =>
+            dispatch(toggleInvertedAndRefresh(heapWorker))
         }),
 
         dom.div({ id: "memory-tool-container" }, [
           List({
             itemComponent: SnapshotListItem,
             items: snapshots,
             onClick: snapshot => dispatch(selectSnapshotAndRefresh(heapWorker, snapshot))
           }),
--- a/devtools/client/memory/components/heap.js
+++ b/devtools/client/memory/components/heap.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { safeErrorString } = require("devtools/shared/DevToolsUtils");
 const Tree = createFactory(require("./tree"));
 const TreeItem = createFactory(require("./tree-item"));
-const { getSnapshotStatusText } = require("../utils");
+const { getSnapshotStatusTextFull } = require("../utils");
 const { snapshotState: states } = require("../constants");
 const { snapshot: snapshotModel } = require("../models");
 const TAKE_SNAPSHOT_TEXT = "Take snapshot";
 // 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;
 
 /**
@@ -35,18 +36,16 @@ function createParentMap (node, aggregat
  *
  * @param {CensusTreeNode} census
  * @return {Object}
  */
 function createTreeProperties (census) {
   let map = createParentMap(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: HEAP_TREE_ROW_HEIGHT,
   };
 }
@@ -62,45 +61,57 @@ const Heap = module.exports = createClas
 
   propTypes: {
     onSnapshotClick: PropTypes.func.isRequired,
     snapshot: snapshotModel,
   },
 
   render() {
     let { snapshot, onSnapshotClick } = this.props;
-    let pane;
     let census = snapshot ? snapshot.census : null;
     let state = snapshot ? snapshot.state : "initial";
+    let statusText = snapshot ? getSnapshotStatusTextFull(snapshot) : "";
+    let content;
 
     switch (state) {
       case "initial":
-        pane = dom.div({ className: "heap-view-panel", "data-state": "initial" },
-          dom.button({ className: "take-snapshot", onClick: onSnapshotClick }, TAKE_SNAPSHOT_TEXT)
-        );
+        content = dom.button({
+          className: "devtools-toolbarbutton take-snapshot",
+          onClick: onSnapshotClick,
+          // Want to use the [standalone] tag to leverage our styles,
+          // but React hates that evidently
+          "data-standalone": true,
+          "data-text-only": true,
+        }, TAKE_SNAPSHOT_TEXT)
+        break;
+      case states.ERROR:
+        content = [
+          dom.span({ className: "snapshot-status error" }, statusText),
+          dom.pre({}, safeErrorString(snapshot.error || new Error("blahblah"))),
+        ];
         break;
       case states.SAVING:
       case states.SAVED:
       case states.READING:
       case states.READ:
       case states.SAVING_CENSUS:
-        pane = dom.div({ className: "heap-view-panel", "data-state": state },
-          getSnapshotStatusText(snapshot));
+        content = dom.span({ className: "snapshot-status devtools-throbber" }, statusText)
         break;
       case states.SAVED_CENSUS:
-        pane = dom.div({ className: "heap-view-panel", "data-state": "loaded" },
+        content = [
           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;
     }
+    let pane = dom.div({ className: "heap-view-panel", "data-state": state }, content);
 
     return (
       dom.div({ id: "heap-view", "data-state": state }, pane)
     )
   }
 });
--- a/devtools/client/memory/components/toolbar.js
+++ b/devtools/client/memory/components/toolbar.js
@@ -11,43 +11,57 @@ const Toolbar = module.exports = createC
   propTypes: {
     breakdowns: PropTypes.arrayOf(PropTypes.shape({
       name: PropTypes.string.isRequired,
       displayName: PropTypes.string.isRequired,
     })).isRequired,
     onTakeSnapshotClick: PropTypes.func.isRequired,
     onBreakdownChange: PropTypes.func.isRequired,
     onToggleRecordAllocationStacks: PropTypes.func.isRequired,
-    allocations: models.allocations
+    allocations: models.allocations,
+    onToggleInverted: PropTypes.func.isRequired,
+    inverted: PropTypes.bool.isRequired,
   },
 
   render() {
     let {
       onTakeSnapshotClick,
       onBreakdownChange,
       breakdowns,
       onToggleRecordAllocationStacks,
       allocations,
+      onToggleInverted,
+      inverted
     } = this.props;
 
     return (
       DOM.div({ className: "devtools-toolbar" }, [
         DOM.button({ className: `take-snapshot devtools-button`, onClick: onTakeSnapshotClick }),
 
         DOM.select({
           className: `select-breakdown`,
           onChange: e => onBreakdownChange(e.target.value),
         }, breakdowns.map(({ name, displayName }) => DOM.option({ value: name }, displayName))),
 
         DOM.label({}, [
           DOM.input({
             type: "checkbox",
+            checked: inverted,
+            onChange: onToggleInverted,
+          }),
+          // TODO bug 1214799
+          "Invert tree"
+        ]),
+
+        DOM.label({}, [
+          DOM.input({
+            type: "checkbox",
             checked: allocations.recording,
             disabled: allocations.togglingInProgress,
             onChange: onToggleRecordAllocationStacks,
           }),
           // TODO bug 1214799
           "Record allocation stacks"
         ])
-      ])
+])
     );
   }
 });
--- a/devtools/client/memory/components/tree-item.js
+++ b/devtools/client/memory/components/tree-item.js
@@ -1,29 +1,52 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
 const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+
 const INDENT = 10;
+const MAX_SOURCE_LENGTH = 200;
+
 
 /**
  * An arrow that displays whether its node is expanded (▼) or collapsed
  * (▶). 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" },
+    return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` },
       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-name", style: { marginLeft: depth * INDENT }},
         arrow,
-        item.name
+        this.toLabel(item.name)
       )
     );
+  },
+
+  toLabel(name) {
+    return isSavedFrame(name)
+      ? this.savedFrameToLabel(name)
+      : String(name);
+  },
+
+  savedFrameToLabel(frame) {
+    return [
+      dom.span({ className: "heap-tree-item-function-display-name" },
+               frame.functionDisplayFrame || ""),
+      dom.span({ className: "heap-tree-item-at" }, "@"),
+      dom.span({ className: "heap-tree-item-source" }, frame.source.slice(0, MAX_SOURCE_LENGTH)),
+      dom.span({ className: "heap-tree-item-colon" }, ":"),
+      dom.span({ className: "heap-tree-item-line" }, frame.line),
+      dom.span({ className: "heap-tree-item-colon" }, ":"),
+      dom.span({ className: "heap-tree-item-column" }, frame.column)
+    ];
   }
 });
--- a/devtools/client/memory/constants.js
+++ b/devtools/client/memory/constants.js
@@ -21,16 +21,22 @@ actions.TAKE_CENSUS_END = "take-census-e
 
 // When requesting that the server start/stop recording allocation stacks.
 actions.TOGGLE_RECORD_ALLOCATION_STACKS_START = "toggle-record-allocation-stacks-start";
 actions.TOGGLE_RECORD_ALLOCATION_STACKS_END = "toggle-record-allocation-stacks-end";
 
 // Fired by UI to select a snapshot to view.
 actions.SELECT_SNAPSHOT = "select-snapshot";
 
+// Fired to toggle tree inversion on or off.
+actions.TOGGLE_INVERTED = "toggle-inverted";
+
+// Fired when there is an error processing a snapshot or taking a census.
+actions.SNAPSHOT_ERROR = "snapshot-error";
+
 // Options passed to MemoryFront's startRecordingAllocations never change.
 exports.ALLOCATION_RECORDING_OPTIONS = {
   probability: 1,
   maxLogLength: 1
 };
 
 const COUNT = { by: "count", count: true, bytes: true };
 const INTERNAL_TYPE = { by: "internalType", then: COUNT };
@@ -66,18 +72,22 @@ const breakdowns = exports.breakdowns = 
 };
 
 const snapshotState = exports.snapshotState = {};
 
 /**
  * Various states a snapshot can be in.
  * An FSM describing snapshot states:
  *
- * SAVING -> SAVED -> READING -> READ   <-  <-  <- SAVED_CENSUS
- *                                    ↘             ↗
- *                                     SAVING_CENSUS
+ *     SAVING -> SAVED -> READING -> READ   <-  <-  <- SAVED_CENSUS
+ *                                        ↘             ↗
+ *                                         SAVING_CENSUS
+ *
+ * Any of these states may go to the ERROR state, from which they can never
+ * leave (mwah ha ha ha!)
  */
+snapshotState.ERROR = "snapshot-state-error";
 snapshotState.SAVING = "snapshot-state-saving";
 snapshotState.SAVED = "snapshot-state-saved";
 snapshotState.READING = "snapshot-state-reading";
 snapshotState.READ = "snapshot-state-read";
 snapshotState.SAVING_CENSUS = "snapshot-state-saving-census";
 snapshotState.SAVED_CENSUS = "snapshot-state-saved-census";
--- a/devtools/client/memory/models.js
+++ b/devtools/client/memory/models.js
@@ -26,16 +26,20 @@ let snapshotModel = exports.snapshot = P
   selected: PropTypes.bool.isRequired,
   // fs path to where the snapshot is stored; used to
   // identify the snapshot for HeapAnalysesClient.
   path: PropTypes.string,
   // Data of a census breakdown
   census: PropTypes.object,
   // The breakdown used to generate the current census
   breakdown: breakdownModel,
+  // Whether the currently cached census tree is inverted or not.
+  inverted: PropTypes.bool,
+  // If an error was thrown while processing this snapshot, the `Error` instance is attached here.
+  error: PropTypes.object,
   // State the snapshot is in
   // @see ./constants.js
   state: function (props, propName) {
     let stateNames = Object.keys(states);
     let current = props.state;
     let shouldHavePath = [states.SAVED, states.READ, states.SAVING_CENSUS, states.SAVED_CENSUS];
     let shouldHaveCensus = [states.SAVED_CENSUS];
 
@@ -58,18 +62,20 @@ let allocationsModel = exports.allocatio
   // stacks on or off right now.
   togglingInProgress: PropTypes.bool.isRequired,
 });
 
 let appModel = exports.app = {
   // {MemoryFront} Used to communicate with platform
   front: PropTypes.instanceOf(MemoryFront),
   // Allocations recording related data.
-  allocations: allocationsModel,
+  allocations: allocationsModel.isRequired,
   // {HeapAnalysesClient} Used to interface with snapshots
   heapWorker: PropTypes.instanceOf(HeapAnalysesClient),
   // The breakdown object DSL describing how we want
   // the census data to be.
   // @see `js/src/doc/Debugger/Debugger.Memory.md`
   breakdown: breakdownModel.isRequired,
   // List of reference to all snapshots taken
   snapshots: PropTypes.arrayOf(snapshotModel).isRequired,
+  // True iff we want the tree displayed inverted.
+  inverted: PropTypes.bool.isRequired,
 };
deleted file mode 100644
--- a/devtools/client/memory/modules/census-view.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-/**
- * This file contains the tree view, displaying all the samples and frames
- * received from the proviler in a tree-like structure.
- */
-
-const { Cc, Ci, Cu, Cr } = require("chrome");
-const { Heritage } = require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
-const { AbstractTreeItem } = require("resource://devtools/client/shared/widgets/AbstractTreeItem.jsm");
-
-const INDENTATION = exports.INDENTATION = 16; // px
-const DEFAULT_AUTO_EXPAND_DEPTH = 2;
-const COURSE_TYPES = ["objects", "scripts", "strings", "other"];
-
-/**
- * Every instance of a `CensusView` represents a row in the census tree. The same
- * parent node is used for all rows.
- *
- * @param CensusView parent
- *        The CensusView considered the parent row. Should be null
- *        for root node.
- * @param {CensusTreeNode} censusTreeNode
- *        Data from `takeCensus` transformed via `CensusTreeNode`.
- *        @see browser/toolkit/heapsnapshot/census-tree-node.js
- * @param number level [optional]
- *        The indentation level in the call tree. The root node is at level 0.
- * @param boolean hidden [optional]
- *        Whether this node should be hidden and not contribute to depth/level
- *        calculations. Defaults to false.
- * @param number autoExpandDepth [optional]
- *        The depth to which the tree should automatically expand. Defaults to
- *        the caller's `autoExpandDepth` if a caller exists, otherwise defaults
- *        to DEFAULT_AUTO_EXPAND_DEPTH.
- */
-function CensusView ({ caller, censusTreeNode, level, hidden, autoExpandDepth }) {
-  AbstractTreeItem.call(this, { parent: caller, level: level|0 - (hidden ? 1 : 0) });
-
-  this.autoExpandDepth = autoExpandDepth != null
-    ? autoExpandDepth
-    : caller ? caller.autoExpandDepth
-             : DEFAULT_AUTO_EXPAND_DEPTH;
-
-  this.caller = caller;
-  this.censusTreeNode = censusTreeNode;
-  this.hidden = hidden;
-};
-
-CensusView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
-  /**
-   * Creates the view for this tree node.
-   * @param nsIDOMNode document
-   * @param nsIDOMNode arrowNode
-   * @return nsIDOMNode
-   */
-  _displaySelf: function (document, arrowNode) {
-    let data = this.censusTreeNode;
-
-    let cells = [];
-
-    // Only use an arrow if there are children
-    if (data.children && data.children.length) {
-      cells.push(arrowNode);
-    }
-
-    cells.push(this._createCell(document, data.name, "name"));
-
-    if (data.bytes != null) {
-      cells.push(this._createCell(document, data.bytes, "bytes"));
-    }
-    if (data.count != null) {
-      cells.push(this._createCell(document, data.count, "count"));
-    }
-
-    let targetNode = document.createElement("li");
-    targetNode.className = "heap-tree-item";
-    targetNode.style.MozMarginStart = `${this.level * INDENTATION}px`;
-    if (this.hidden) {
-      targetNode.style.display = "none";
-    }
-
-    for (let i = 0; i < cells.length; i++) {
-      targetNode.appendChild(cells[i]);
-    }
-
-    return targetNode;
-  },
-
-  /**
-   * Populates this node in the call tree with the corresponding "callees".
-   * These are defined in the `frame` data source for this call view.
-   * @param array:AbstractTreeItem children
-   */
-  _populateSelf: function (children) {
-    let newLevel = this.level + 1;
-    let data = this.censusTreeNode;
-
-    if (data.children) {
-      for (let node of data.children) {
-        children.push(new CensusView({
-          caller: this,
-          level: newLevel,
-          censusTreeNode: node,
-        }));
-      }
-    }
-  },
-
-  /**
-   * Functions creating each cell in this call view.
-   * Invoked by `_displaySelf`.
-   */
-  _createCell: function (doc, value, type) {
-    let cell = doc.createElement("span");
-    cell.className = "plain heap-tree-cell";
-    cell.setAttribute("type", type);
-    cell.innerHTML = value;
-    return cell;
-  },
-});
-
-exports.CensusView = CensusView;
deleted file mode 100644
--- a/devtools/client/memory/modules/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# vim: set filetype=python:
-# 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/.
-
-DevToolsModules(
-    'census-view.js',
-)
--- a/devtools/client/memory/moz.build
+++ b/devtools/client/memory/moz.build
@@ -1,26 +1,24 @@
 # vim: set filetype=python:
 # 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/.
 
 DIRS += [
     'actions',
     'components',
-    'modules',
     'reducers',
 ]
 
 DevToolsModules(
     'app.js',
     'constants.js',
     'initializer.js',
     'models.js',
     'panel.js',
     'reducers.js',
     'store.js',
     'utils.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
-MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
--- a/devtools/client/memory/reducers.js
+++ b/devtools/client/memory/reducers.js
@@ -2,8 +2,9 @@
  * 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";
 
 exports.allocations = require("./reducers/allocations");
 exports.snapshots = require("./reducers/snapshots");
 exports.breakdown = require("./reducers/breakdown");
 exports.errors = require("./reducers/errors");
+exports.inverted = require("./reducers/inverted");
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/reducers/inverted.js
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { actions } = require("../constants");
+
+module.exports = function (inverted = false, action) {
+  return action.type === actions.TOGGLE_INVERTED ? !inverted : inverted;
+};
--- a/devtools/client/memory/reducers/moz.build
+++ b/devtools/client/memory/reducers/moz.build
@@ -2,10 +2,11 @@
 # 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/.
 
 DevToolsModules(
     'allocations.js',
     'breakdown.js',
     'errors.js',
+    'inverted.js',
     'snapshots.js',
 )
--- a/devtools/client/memory/reducers/snapshots.js
+++ b/devtools/client/memory/reducers/snapshots.js
@@ -2,16 +2,23 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { actions, snapshotState: states } = require("../constants");
 const { getSnapshot } = require("../utils");
 
 let handlers = Object.create({});
 
+handlers[actions.SNAPSHOT_ERROR] = function (snapshots, action) {
+  let snapshot = getSnapshot(snapshots, action.snapshot);
+  snapshot.state = states.ERROR;
+  snapshot.error = action.error;
+  return [...snapshots];
+};
+
 handlers[actions.TAKE_SNAPSHOT_START] = function (snapshots, { snapshot }) {
   return [...snapshots, snapshot];
 };
 
 handlers[actions.TAKE_SNAPSHOT_END] = function (snapshots, action) {
   return snapshots.map(snapshot => {
     if (snapshot.id === action.snapshot.id) {
       snapshot.state = states.SAVED;
@@ -33,24 +40,26 @@ handlers[actions.READ_SNAPSHOT_END] = fu
   return [...snapshots];
 };
 
 handlers[actions.TAKE_CENSUS_START] = function (snapshots, action) {
   let snapshot = getSnapshot(snapshots, action.snapshot);
   snapshot.state = states.SAVING_CENSUS;
   snapshot.census = null;
   snapshot.breakdown = action.breakdown;
+  snapshot.inverted = action.inverted;
   return [...snapshots];
 };
 
 handlers[actions.TAKE_CENSUS_END] = function (snapshots, action) {
   let snapshot = getSnapshot(snapshots, action.snapshot);
   snapshot.state = states.SAVED_CENSUS;
   snapshot.census = action.census;
   snapshot.breakdown = action.breakdown;
+  snapshot.inverted = action.inverted;
   return [...snapshots];
 };
 
 handlers[actions.SELECT_SNAPSHOT] = function (snapshots, action) {
   return snapshots.map(s => {
     s.selected = s.id === action.snapshot.id;
     return s;
   });
deleted file mode 100644
--- a/devtools/client/memory/test/mochitest/chrome.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-tags = devtools
-skip-if = buildapp == 'b2g' || os == 'android'
-support-files =
-  head.js
-
-[test_census-view-01.html]
deleted file mode 100644
--- a/devtools/client/memory/test/mochitest/head.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-var Cr = Components.results;
-var CC = Components.Constructor;
-
-const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
deleted file mode 100644
--- a/devtools/client/memory/test/mochitest/test_census-view-01.html
+++ /dev/null
@@ -1,103 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-Bug 1067491 - Test taking a census over the RDP.
--->
-<head>
-  <meta charset="utf-8">
-  <title>Census Tree 01</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css" />
-  <link href="chrome://devtools/skin/themes/light-theme.css" type="text/css" />
-  <link href="chrome://devtools/skin/themes/common.css" type="text/css" />
-  <link href="chrome://devtools/skin/themes/widgets.css" type="text/css" />
-  <link href="chrome://devtools/skin/themes/memory.css" type="text/css" />
-</head>
-<body>
-<ul id="container" style="width:100%;height:300px;"></ul>
-<pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script>
-window.onload = function() {
-  var { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
-  var { INDENTATION, CensusView } = require("devtools/client/memory/modules/census-view");
-  SimpleTest.waitForExplicitFinish();
-  const countBreakdown = { by: "count", count: true, bytes: true };
-
-  const BREAKDOWN = {
-    by: "coarseType",
-    objects: { by: "objectClass", then: countBreakdown },
-    strings: countBreakdown,
-    scripts: countBreakdown,
-    other: { by: "internalType", then: countBreakdown },
-  };
-
-  const REPORT = {
-    "objects": {
-      "Function": { bytes: 10, count: 1 },
-      "Array": { bytes: 20, count: 2 },
-    },
-    "strings": { bytes: 10, count: 1 },
-    "scripts": { bytes: 20, count: 2 },
-    "other": {
-      "js::Shape": { bytes: 30, count: 3 },
-      "js::Shape2": { bytes: 40, count: 4 }
-    },
-  };
-
-  const EXPECTED_ROWS = [
-    { level: 0, name: "other", bytes: 0, count: 0 },
-        { level: 1, name: "js::Shape2", bytes: 40, count: 4, },
-        { level: 1, name: "js::Shape", bytes: 30, count: 3, },
-    { level: 0, name: "objects", bytes: 0, count: 0 },
-        { level: 1, name: "Array", bytes: 20, count: 2, },
-        { level: 1, name: "Function", bytes: 10, count: 1, },
-    { level: 0, name: "scripts", bytes: 20, count: 2, },
-    { level: 0, name: "strings", bytes: 10, count: 1, },
-  ];
-  var censusTreeNode = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
-
-  var view = new CensusView({
-    censusTreeNode: censusTreeNode,
-    hidden: true
-  });
-
-  view.attachTo(document.querySelector("#container"));
-
-  var ul = document.querySelector("#container");
-  var children = Array.from(ul.children).filter(n => n.style.display !== "none");
-
-  for (var i = 0; i < children.length; i++) {
-    var el = children[i];
-    var expected = EXPECTED_ROWS[i];
-    var nameEl = el.querySelector(".heap-tree-cell[type='name']");
-    var bytesEl = el.querySelector(".heap-tree-cell[type='bytes']");
-    var countEl = el.querySelector(".heap-tree-cell[type='count']");
-
-    is(nameEl.innerHTML, expected.name,
-      `correct name "${expected.name}" in heap tree`);
-
-    is(el.style.MozMarginStart, (INDENTATION * expected.level) + "px",
-      `correct indentation for ${expected.name}`);
-
-    if ("bytes" in expected) {
-      is(bytesEl.innerHTML, String(expected.bytes),
-        `correct bytes "${expected.bytes}" in heap tree`);
-    } else {
-      ok(!bytesEl, `no bytes correctly displayed for ${expected.name}`);
-    }
-
-    if ("count" in expected) {
-      is(countEl.innerHTML, String(expected.count),
-        `correct count "${expected.count}" in heap tree`);
-    } else {
-      ok(!countEl, `no count correctly displayed for ${expected.name}`);
-    }
-  }
-
-  SimpleTest.finish();
-};
-</script>
-</pre>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-01.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that changing inverted state properly refreshes the selected census.
+
+let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
+let { toggleInvertedAndRefresh } = require("devtools/client/memory/actions/inverted");
+let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  let { getState, dispatch } = store;
+
+  equal(getState().inverted, false, "not inverted by default");
+
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS]);
+  ok(true, "saved 3 snapshots and took a census of each of them");
+
+  dispatch(toggleInvertedAndRefresh(heapWorker));
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS,
+                                       states.SAVING_CENSUS]);
+  ok(true, "toggling inverted should recompute the selected snapshot's census");
+
+  equal(getState().inverted, true, "now inverted");
+
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS]);
+
+  equal(getState().snapshots[0].inverted, false);
+  equal(getState().snapshots[1].inverted, false);
+  equal(getState().snapshots[2].inverted, true);
+
+  dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1]));
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
+                                       states.SAVING_CENSUS,
+                                       states.SAVED_CENSUS]);
+  ok(true, "selecting non-inverted census should trigger a recompute");
+
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS,
+                                       states.SAVED_CENSUS]);
+
+  equal(getState().snapshots[0].inverted, false);
+  equal(getState().snapshots[1].inverted, true);
+  equal(getState().snapshots[2].inverted, true);
+
+  heapWorker.destroy();
+  yield front.detach();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-inverted-and-refresh-02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that changing inverted state in the middle of taking a snapshot results
+// in an inverted census.
+
+let { snapshotState: states } = require("devtools/client/memory/constants");
+let { toggleInverted, toggleInvertedAndRefresh } = require("devtools/client/memory/actions/inverted");
+let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  let { getState, dispatch } = store;
+
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  yield waitUntilSnapshotState(store, [states.SAVING]);
+
+  dispatch(toggleInverted());
+
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
+  ok(getState().inverted,
+     "should want inverted trees");
+  ok(getState().snapshots[0].inverted,
+     "snapshot-we-were-in-the-middle-of-saving's census should be inverted");
+
+  dispatch(toggleInvertedAndRefresh(heapWorker));
+  yield waitUntilSnapshotState(store, [states.SAVING_CENSUS]);
+  ok(true, "toggling inverted retriggers census");
+  ok(!getState().inverted, "no long inverted");
+
+  dispatch(toggleInverted());
+  yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
+  ok(getState().inverted, "inverted again");
+  ok(getState().snapshots[0].inverted,
+     "census-we-were-in-the-middle-of-recomputing should be inverted again");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/unit/test_action-toggle-inverted.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test toggling the top level inversion state of the tree.
+
+let { toggleInverted } = require("devtools/client/memory/actions/inverted");
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function *() {
+  let front = new StubbedMemoryFront();
+  let heapWorker = new HeapAnalysesClient();
+  yield front.attach();
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  equal(getState().inverted, false, "not inverted by default");
+
+  dispatch(toggleInverted());
+  equal(getState().inverted, true, "now inverted after toggling");
+
+  dispatch(toggleInverted());
+  equal(getState().inverted, false, "not inverted again after toggling again");
+
+  heapWorker.destroy();
+  yield front.detach();
+});
--- a/devtools/client/memory/test/unit/xpcshell.ini
+++ b/devtools/client/memory/test/unit/xpcshell.ini
@@ -1,15 +1,18 @@
 [DEFAULT]
 tags = devtools
 head = head.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
+[test_action-toggle-inverted.js]
+[test_action-toggle-inverted-and-refresh-01.js]
+[test_action-toggle-inverted-and-refresh-02.js]
 [test_action-toggle-recording-allocations.js]
 [test_action-select-snapshot.js]
 [test_action-set-breakdown.js]
 [test_action-set-breakdown-and-refresh-01.js]
 [test_action-set-breakdown-and-refresh-02.js]
 [test_action-take-census.js]
 [test_action-take-snapshot.js]
 [test_action-take-snapshot-and-census.js]
--- a/devtools/client/memory/utils.js
+++ b/devtools/client/memory/utils.js
@@ -1,30 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+const { assert } = require("devtools/shared/DevToolsUtils");
 const { Preferences } = require("resource://gre/modules/Preferences.jsm");
 const CUSTOM_BREAKDOWN_PREF = "devtools.memory.custom-breakdowns";
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { snapshotState: states, breakdowns } = require("./constants");
+const FULL_ERROR_TEXT = "There was an error processing this snapshot.";
+const ERROR_SNAPSHOT_TEXT = "⚠ Error!";
 const SAVING_SNAPSHOT_TEXT = "Saving snapshot...";
 const READING_SNAPSHOT_TEXT = "Reading snapshot...";
 const SAVING_CENSUS_TEXT = "Taking heap census...";
 
-// @TODO 1215606
-// Use DevToolsUtils.assert when fixed.
-exports.assert = function (condition, message) {
-  if (!condition) {
-    const err = new Error("Assertion failure: " + message);
-    DevToolsUtils.reportException("DevToolsUtils.assert", err);
-    throw err;
-  }
-};
-
 /**
  * Returns an array of objects with the unique key `name`
  * and `displayName` for each breakdown.
  *
  * @return {Object{name, displayName}}
  */
 exports.getBreakdownDisplayData = function () {
   return exports.getBreakdownNames().map(name => {
@@ -89,45 +82,65 @@ exports.breakdownNameToSpec = function (
   else if (name in breakdowns) {
     return breakdowns[name].breakdown;
   }
   return Object.create(null);
 };
 
 /**
  * Returns a string representing a readable form of the snapshot's state.
+ * More brief than `getSnapshotStatusText`.
  *
  * @param {Snapshot} snapshot
  * @return {String}
  */
 exports.getSnapshotStatusText = function (snapshot) {
-  exports.assert((snapshot || {}).state,
+  assert((snapshot || {}).state,
     `Snapshot must have expected state, found ${(snapshot || {}).state}.`);
 
   switch (snapshot.state) {
+    case states.ERROR:
+      return ERROR_SNAPSHOT_TEXT;
     case states.SAVING:
       return SAVING_SNAPSHOT_TEXT;
     case states.SAVED:
     case states.READING:
       return READING_SNAPSHOT_TEXT;
     case states.SAVING_CENSUS:
       return SAVING_CENSUS_TEXT;
     // If it's read, it shouldn't have any label, as we could've cleared the
     // census cache by changing the breakdown, and we should lazily
     // go to SAVING_CENSUS. If it's SAVED_CENSUS, we have no status to display.
     case states.READ:
     case states.SAVED_CENSUS:
       return "";
   }
 
-  DevToolsUtils.reportException(`Snapshot in unexpected state: ${snapshot.state}`);
+  assert(false, `Snapshot in unexpected state: ${snapshot.state}`);
   return "";
 }
 
 /**
+ * Returns a string representing a readable form of the snapshot's state;
+ * more verbose than `getSnapshotStatusText`.
+ *
+ * @param {Snapshot} snapshot
+ * @return {String}
+ */
+exports.getSnapshotStatusTextFull = function (snapshot) {
+  assert((snapshot || {}).state,
+    `Snapshot must have expected state, found ${(snapshot || {}).state}.`);
+  switch (snapshot.state) {
+    case states.ERROR:
+      return FULL_ERROR_TEXT;
+  }
+  return exports.getSnapshotStatusText(snapshot);
+}
+
+/**
  * Takes an array of snapshots and a snapshot and returns
  * the snapshot instance in `snapshots` that matches
  * the snapshot passed in.
  *
  * @param {Array<Snapshot>} snapshots
  * @param {Snapshot}
  * @return ?Snapshot
  */
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
@@ -3086,17 +3086,18 @@
       setDocumentHeight(cm, op.barMeasure);
     if (op.updatedDisplay || op.startHeight != cm.doc.height)
       updateScrollbars(cm, op.barMeasure);
 
     if (op.selectionChanged) restartBlink(cm);
 
     if (cm.state.focused && op.updateInput)
       cm.display.input.reset(op.typing);
-    if (op.focus && op.focus == activeElt()) ensureFocus(op.cm);
+    if (op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()))
+      ensureFocus(op.cm);
   }
 
   function endOperation_finish(op) {
     var cm = op.cm, display = cm.display, doc = cm.doc;
 
     if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
 
     // Abort mouse wheel delta measurement, when scrolling explicitly
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -20,17 +20,18 @@
 }
 
 html, .theme-body, #app, #memory-tool, #memory-tool-container {
   height: 100%;
 }
 
 .theme-body {
   overflow: hidden;
-  background-color: var(--theme-body-background);
+  /* Not sure why .theme-body declares it as `background` and overrides */
+  background-color: var(--theme-toolbar-background) !important;
 }
 
 #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
@@ -40,37 +41,37 @@ html, .theme-body, #app, #memory-tool, #
 }
 
 /**
  * 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
  */
-.devtools-button.take-snapshot {
+.devtools-toolbar .devtools-button.take-snapshot {
   margin: 2px 1px;
   padding: 1px;
   border-width: 0px;
   /* [standalone] buttons override min-height from 18px to 24px -- why? */
   min-height: 18px;
   /* not sure why this is needed for positioning */
   display: -moz-box;
 }
 
-.devtools-button.take-snapshot::before {
+.devtools-toolbar .devtools-button.take-snapshot::before {
   background-image: url(images/command-screenshot.png);
   -moz-appearance: none;
   width: 16px;
   height: 16px;
   background-size: 64px 16px;
   background-position: 0 center;
   background-repeat: no-repeat;
 }
 @media (min-resolution: 1.1dppx) {
-  .devtools-button.take-snapshot::before {
+  .devtools-toolbar .devtools-button.take-snapshot::before {
     background-image: url(images/command-screenshot@2x.png);
   }
 }
 
 /**
  * TODO bug 1213100
  * Should this be codified in .devtools-toolbar itself?
  */
@@ -104,27 +105,29 @@ html, .theme-body, #app, #memory-tool, #
   margin: 0;
   padding: 0;
   width: 186px;
   list-style-type: none;
   font-size: 12px;
   height: 100%;
   overflow-y: scroll;
   background-color: var(--theme-toolbar-background);
+  color: var(--theme-body-color-alt);
   border-color: var(--theme-splitter-color);
-  color: var(--theme-body-color-alt);
-  border-left-width: 1px;
+  border-style: solid;
+  border-width: 0px 1px 0px 0px;
 }
 
 .list > li {
   height: 40px;
   color: var(--theme-body-color);
   border-bottom: 1px solid rgba(128,128,128,0.15);
   padding: 8px;
   cursor: pointer;
+  color: var(--theme-selection-color);
 }
 
 .list > li.selected {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .snapshot-list-item span {
@@ -143,39 +146,61 @@ html, .theme-body, #app, #memory-tool, #
 
 /**
  * Main panel
  */
 
 #heap-view {
   flex: 1 1 auto;
   border-color: var(--theme-splitter-color);
-  color: var(--theme-body-color)
+  color: var(--theme-body-color);
   border-left-width: 1px;
 }
 
 #heap-view .heap-view-panel {
   width: 100%;
   height: 100%;
+  background-color: var(--theme-toolbar-background)
+}
+
+#heap-view .heap-view-panel .snapshot-status, #heap-view .take-snapshot {
+  font-size: 120%;
+  display: block;
+  margin: 65px auto;
+}
+
+#heap-view .heap-view-panel .snapshot-status {
+  width: 500px;
+  text-align: center;
 }
 
 #heap-view .take-snapshot {
+  padding: 5px;
+}
+
+#heap-view .heap-view-panel[data-state="snapshot-state-error"] pre {
+  background-color: var(--theme-body-background);
+  overflow-y: scroll;
+  height: 100px;
+  margin: 20px;
+  padding: 20px;
 }
 
 /**
  * Heap Tree View
  */
 
 #heap-view .theme-twisty {
   float: left;
 }
 
 .tree {
   height: 100%;
   overflow-y: scroll;
+  background-color: var(--theme-body-background);
 }
 
 .tree .header {
   height: 17px;
   overflow: hidden;
   color: var(--theme-body-color);
   background-color: var(--theme-tab-toolbar-background);
 }
@@ -201,18 +226,19 @@ html, .theme-body, #app, #memory-tool, #
 .tree-node:nth-child(2n) {
   background-color: var(--row-alt-background-color);
 }
 
 .tree-node:hover {
   background-color: var(--row-hover-background-color);
 }
 
-.tree-node:focus {
+.tree-node:focus, .heap-tree-item.focused {
   background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
 }
 
 .header {
   background-color: var(--theme-tab-toolbar-background);
 }
 
 .header span {
   white-space: nowrap;
@@ -239,8 +265,25 @@ html, .theme-body, #app, #memory-tool, #
 .heap-tree-item-bytes,
 .heap-tree-item-total-bytes {
   width: 7vw;
 }
 
 .heap-tree-item-name {
   padding-left: 10px;
 }
+
+.error::before {
+  content: "";
+  background-image: url(chrome://devtools/skin/themes/images/webconsole.svg);
+  background-repeat: no-repeat;
+  background-size: 72px 60px;
+  width: 12px;
+  height: 12px;
+  display: inline-block;
+
+  background-position: -24px -24px;
+  margin: 2px 5px 0 0;
+  max-height: 12px;
+}
+.theme-light .error::before {
+  background-image: url(chrome://devtools/skin/themes/images/webconsole.svg#light-icons);
+}
--- a/devtools/client/themes/toolbars.inc.css
+++ b/devtools/client/themes/toolbars.inc.css
@@ -74,17 +74,17 @@
 }
 
 .devtools-menulist:-moz-focusring,
 .devtools-toolbarbutton:-moz-focusring {
   outline: 1px dotted hsla(210,30%,85%,0.7);
   outline-offset: -4px;
 }
 
-.devtools-toolbarbutton[standalone] {
+.devtools-toolbarbutton[standalone], .devtools-toolbarbutton[data-standalone] {
   -moz-margin-end: 5px;
   border-width: 1px;
   border-style: solid;
 }
 .devtools-toolbarbutton[label][standalone] {
   min-height: 2em;
 }
 
@@ -159,20 +159,22 @@
 }
 .theme-light .devtools-menulist,
 .theme-light .devtools-toolbarbutton {
   border-color: rgba(170, 170, 170, .5); /* Splitters */
 }
 
 /* Text-only buttons */
 .theme-light .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]),
+.theme-light .devtools-toolbarbutton[data-text-only],
 .theme-light #toolbox-buttons .devtools-toolbarbutton[text-as-image] {
   background-color: rgba(170, 170, 170, .2); /* Splitter */
 }
 .theme-dark .devtools-toolbarbutton[label]:not([text-as-image]):not([type=menu-button]),
+.theme-dark .devtools-toolbarbutton[data-text-only],
 .theme-dark #toolbox-buttons .devtools-toolbarbutton[text-as-image] {
   background-color: rgba(0, 0, 0, .2); /* Splitter */
 }
 
 /* Button States */
 .theme-dark .devtools-toolbarbutton:not([disabled]):hover,
 .theme-dark #toolbox-buttons .devtools-toolbarbutton:not([disabled])[text-as-image]:hover,
 .theme-dark .devtools-toolbarbutton:not([disabled])[label]:not([text-as-image]):not([type=menu-button]):hover {
@@ -265,17 +267,17 @@
   margin: 0;
   padding: 0;
   min-width: 32px;
   min-height: 18px;
   /* The icon is absolutely positioned in the button using ::before */
   position: relative;
 }
 
-.devtools-button[standalone] {
+.devtools-button[standalone], .devtools-button[data-standalone] {
   min-height: 32px;
   border-width: 1px;
 }
 
 /* Button States */
 .theme-dark .devtools-button:not([disabled]):hover {
   background: rgba(0, 0, 0, .3); /* Splitters */
 }
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -813,8 +813,15 @@ exports.openFileStream = function (fileP
 
 exports.isGenerator = function (fn) {
   return typeof fn === "function" && fn.isGenerator();
 };
 
 exports.isPromise = function (p) {
   return p && typeof p.then === "function";
 };
+
+/**
+ * Return true if `thing` is a SavedFrame, false otherwise.
+ */
+exports.isSavedFrame = function (thing) {
+  return Object.prototype.toString.call(thing) === "[object SavedFrame]";
+};
--- a/devtools/shared/heapsnapshot/CoreDump.proto
+++ b/devtools/shared/heapsnapshot/CoreDump.proto
@@ -105,17 +105,17 @@ message StackFrame {
 message Node {
     optional uint64     id                   = 1;
 
     // De-duplicated two-byte string.
     oneof TypeNameOrRef {
         bytes           typeName             = 2;
         uint64          typeNameRef          = 3;
     }
-    
+
     optional uint64     size                 = 4;
     repeated Edge       edges                = 5;
     optional StackFrame allocationStack      = 6;
 
     // De-duplicated one-byte string.
     oneof JSObjectClassNameOrRef {
         bytes           jsObjectClassName    = 7;
         uint64          jsObjectClassNameRef = 8;
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -99,28 +99,38 @@ HeapSnapshot::Create(JSContext* cx,
 template<typename MessageType>
 static bool
 parseMessage(ZeroCopyInputStream& stream, MessageType& message)
 {
   // We need to create a new `CodedInputStream` for each message so that the
   // 64MB limit is applied per-message rather than to the whole stream.
   CodedInputStream codedStream(&stream);
 
+  // The protobuf message nesting that core dumps exhibit is dominated by
+  // allocation stacks' frames. In the most deeply nested case, each frame has
+  // two messages: a StackFrame message and a StackFrame::Data message. These
+  // frames are on top of a small constant of other messages. There are a
+  // MAX_STACK_DEPTH number of frames, so we multiply this by 3 to make room for
+  // the two messages per frame plus some head room for the constant number of
+  // non-dominating messages.
+  codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
+
   // Because protobuf messages aren't self-delimiting, we serialize each message
   // preceeded by its size in bytes. When deserializing, we read this size and
   // then limit reading from the stream to the given byte size. If we didn't,
   // then the first message would consume the entire stream.
 
   uint32_t size = 0;
   if (NS_WARN_IF(!codedStream.ReadVarint32(&size)))
     return false;
 
   auto limit = codedStream.PushLimit(size);
   if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
-      NS_WARN_IF(!codedStream.ConsumedEntireMessage()))
+      NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
+      NS_WARN_IF(codedStream.BytesUntilLimit() != 0))
   {
     return false;
   }
 
   codedStream.PopLimit(limit);
   return true;
 }
 
@@ -904,17 +914,18 @@ class MOZ_STACK_CLASS StreamWriter : pub
     uint64_t ref = oneByteStringsAlreadySerialized.count();
     if (!oneByteStringsAlreadySerialized.add(ptr, string, ref))
       return false;
 
     setString(stringData.release());
     return true;
   }
 
-  protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame) {
+  protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame,
+                                              size_t depth = 1) {
     // NB: de-duplicated string properties must be written in the same order
     // here as they are read in `HeapSnapshot::saveStackFrame` or else indices
     // in references to already serialized strings will be off.
 
     MOZ_ASSERT(frame,
                "null frames should be represented as the lack of a serialized "
                "stack frame");
 
@@ -952,18 +963,18 @@ class MOZ_STACK_CLASS StreamWriter : pub
                                [&] (std::string* name) { data->set_allocated_functiondisplayname(name); },
                                [&] (uint64_t ref) { data->set_functiondisplaynameref(ref); }))
       {
         return nullptr;
       }
     }
 
     auto parent = frame.parent();
-    if (parent) {
-      auto protobufParent = getProtobufStackFrame(parent);
+    if (parent && depth < HeapSnapshot::MAX_STACK_DEPTH) {
+      auto protobufParent = getProtobufStackFrame(parent, depth + 1);
       if (!protobufParent)
         return nullptr;
       data->set_allocated_parent(protobufParent);
     }
 
     protobufStackFrame->set_allocated_data(data.release());
 
     if (!framesAlreadySerialized.put(id))
--- a/devtools/shared/heapsnapshot/HeapSnapshot.h
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -66,16 +66,23 @@ class HeapSnapshot final : public nsISup
   bool saveNode(const protobuf::Node& node);
 
   // Save the given `protobuf::StackFrame` message in this `HeapSnapshot` as a
   // `DeserializedStackFrame`. The saved stack frame's id is returned via the
   // out parameter.
   bool saveStackFrame(const protobuf::StackFrame& frame,
                       StackFrameId& outFrameId);
 
+public:
+  // The maximum number of stack frames that we will serialize into a core
+  // dump. This helps prevent over-recursion in the protobuf library when
+  // deserializing stacks.
+  static const size_t MAX_STACK_DEPTH = 60;
+
+private:
   // If present, a timestamp in the same units that `PR_Now` gives.
   Maybe<uint64_t> timestamp;
 
   // The id of the root node for this deserialized heap graph.
   NodeId rootId;
 
   // The set of nodes in this deserialized heap graph, keyed by id.
   using NodeSet = js::HashSet<DeserializedNode, DeserializedNode::HashPolicy>;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_deepStack_01.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can save a core dump with very deep allocation stacks and read
+// it back into a HeapSnapshot.
+
+function stackDepth(stack) {
+  return stack ? 1 + stackDepth(stack.parent) : 0;
+}
+
+function run_test() {
+  // Create a Debugger observing a debuggee's allocations.
+  const debuggee = new Cu.Sandbox(null);
+  const dbg = new Debugger(debuggee);
+  dbg.memory.trackingAllocationSites = true;
+
+  // Allocate some objects in the debuggee that will have their allocation
+  // stacks recorded by the Debugger.
+
+  debuggee.eval("this.objects = []");
+  debuggee.eval(
+    (function recursiveAllocate(n) {
+      if (n <= 0)
+        return;
+
+      // Make sure to recurse before pushing the object so that when TCO is
+      // implemented sometime in the future, it doesn't invalidate this test.
+      recursiveAllocate(n - 1);
+      this.objects.push({});
+    }).toString()
+  );
+  debuggee.eval("recursiveAllocate = recursiveAllocate.bind(this);");
+  debuggee.eval("recursiveAllocate(200);");
+
+  // Now save a snapshot that will include the allocation stacks and read it
+  // back again.
+
+  const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
+  ok(true, "Should be able to save a snapshot.");
+
+  const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+  ok(snapshot, "Should be able to read a heap snapshot");
+  ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
+
+  const report = snapshot.takeCensus({
+    breakdown: { by: "allocationStack",
+                 then: { by: "count", bytes: true, count: true },
+                 noStack: { by: "count", bytes: true, count: true }
+               }
+  });
+
+  // Keep this synchronized with `HeapSnapshot::MAX_STACK_DEPTH`!
+  const MAX_STACK_DEPTH = 60;
+
+  let foundStacks = false;
+  report.forEach((v, k) => {
+    if (k === "noStack") {
+      return;
+    }
+
+    foundStacks = true;
+    const depth = stackDepth(k);
+    dumpn("Stack depth is " + depth);
+    ok(depth <= MAX_STACK_DEPTH,
+       "Every stack should have depth less than or equal to the maximum stack depth");
+  });
+  ok(foundStacks);
+
+  do_test_finished();
+}
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -29,16 +29,17 @@ support-files =
 [test_HeapAnalyses_takeCensus_01.js]
 [test_HeapAnalyses_takeCensus_02.js]
 [test_HeapAnalyses_takeCensus_03.js]
 [test_HeapAnalyses_takeCensus_04.js]
 [test_HeapAnalyses_takeCensus_05.js]
 [test_HeapAnalyses_takeCensus_06.js]
 [test_HeapAnalyses_takeCensus_07.js]
 [test_HeapSnapshot_creationTime_01.js]
+[test_HeapSnapshot_deepStack_01.js]
 [test_HeapSnapshot_takeCensus_01.js]
 [test_HeapSnapshot_takeCensus_02.js]
 [test_HeapSnapshot_takeCensus_03.js]
 [test_HeapSnapshot_takeCensus_04.js]
 [test_HeapSnapshot_takeCensus_05.js]
 [test_HeapSnapshot_takeCensus_06.js]
 [test_HeapSnapshot_takeCensus_07.js]
 [test_HeapSnapshot_takeCensus_08.js]
new file mode 100644
--- /dev/null
+++ b/dom/base/ChromeNodeList.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChromeNodeList.h"
+#include "mozilla/dom/ChromeNodeListBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+already_AddRefed<ChromeNodeList>
+ChromeNodeList::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobal.GetAsSupports());
+  nsIDocument* root = win ? win->GetExtantDoc() : nullptr;
+  RefPtr<ChromeNodeList> list = new ChromeNodeList(root);
+  return list.forget();
+}
+
+JSObject*
+ChromeNodeList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ChromeNodeListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+ChromeNodeList::Append(nsINode& aNode, ErrorResult& aError)
+{
+  if (!aNode.IsContent()) {
+    // nsINodeList deals with nsIContent objects only, so need to
+    // filter out other nodes for now.
+    aError.Throw(NS_ERROR_DOM_TYPE_ERR);
+    return;
+  }
+
+  AppendElement(aNode.AsContent());
+}
+
+void
+ChromeNodeList::Remove(nsINode& aNode, ErrorResult& aError)
+{
+  if (!aNode.IsContent()) {
+    aError.Throw(NS_ERROR_DOM_TYPE_ERR);
+    return;
+  }
+
+  RemoveElement(aNode.AsContent());
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/ChromeNodeList.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMArray.h"
+#include "nsContentList.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class GlobalObject;
+
+class ChromeNodeList final : public nsSimpleContentList
+{
+public:
+  explicit ChromeNodeList(nsINode* aOwner)
+  : nsSimpleContentList(aOwner)
+  {
+  }
+
+  static already_AddRefed<ChromeNodeList>
+  Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  void Append(nsINode& aNode, ErrorResult& aError);
+  void Remove(nsINode& aNode, ErrorResult& aError);
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/base/ScreenOrientation.cpp
+++ b/dom/base/ScreenOrientation.cpp
@@ -11,16 +11,17 @@
 #include "nsScreen.h"
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/Hal.h"
 #include "mozilla/Preferences.h"
 
 #include "mozilla/dom/Promise.h"
 
+using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ScreenOrientation,
                                    DOMEventTargetHelper,
                                    mScreen);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ScreenOrientation)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -149,16 +149,17 @@ EXPORTS.mozilla += [
 ]
 
 EXPORTS.mozilla.dom += [
     'AnonymousContent.h',
     'Attr.h',
     'BarProps.h',
     'BlobSet.h',
     'ChildIterator.h',
+    'ChromeNodeList.h',
     'ChromeUtils.h',
     'Comment.h',
     'Console.h',
     'DirectionalityUtils.h',
     'DocumentFragment.h',
     'DocumentType.h',
     'DOMCursor.h',
     'DOMError.h',
@@ -212,16 +213,17 @@ EXPORTS.mozilla.dom += [
     'WindowOrientationObserver.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
     'Attr.cpp',
     'BarProps.cpp',
     'ChildIterator.cpp',
+    'ChromeNodeList.cpp',
     'ChromeUtils.cpp',
     'Comment.cpp',
     'Console.cpp',
     'Crypto.cpp',
     'DirectionalityUtils.cpp',
     'DocumentFragment.cpp',
     'DocumentType.cpp',
     'DOMCursor.cpp',
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7367,36 +7367,94 @@ nsContentUtils::CallOnAllRemoteChildren(
       CallOnAllRemoteChildren(windowMM, aCallback, aArg);
     }
   }
 }
 
 void
 nsContentUtils::TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
                                                 nsTArray<IPCDataTransfer>& aIPC,
+                                                bool aInSyncMessage,
                                                 mozilla::dom::nsIContentChild* aChild,
                                                 mozilla::dom::nsIContentParent* aParent)
 {
   aIPC.Clear();
   if (aTransferables) {
     uint32_t transferableCount = 0;
     aTransferables->Count(&transferableCount);
     for (uint32_t i = 0; i < transferableCount; ++i) {
       IPCDataTransfer* dt = aIPC.AppendElement();
       nsCOMPtr<nsISupports> genericItem;
       aTransferables->GetElementAt(i, getter_AddRefs(genericItem));
       nsCOMPtr<nsITransferable> transferable(do_QueryInterface(genericItem));
-      TransferableToIPCTransferable(transferable, dt, aChild, aParent);
-    }
-  }
+      TransferableToIPCTransferable(transferable, dt, aInSyncMessage, aChild, aParent);
+    }
+  }
+}
+
+nsresult
+nsContentUtils::SlurpFileToString(nsIFile* aFile, nsACString& aString)
+{
+  aString.Truncate();
+
+  nsCOMPtr<nsIURI> fileURI;
+  nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_NewChannel(getter_AddRefs(channel),
+                     fileURI,
+                     nsContentUtils::GetSystemPrincipal(),
+                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                     nsIContentPolicy::TYPE_OTHER);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIInputStream> stream;
+  rv = channel->Open2(getter_AddRefs(stream));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = NS_ConsumeStream(stream, UINT32_MAX, aString);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = stream->Close();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+bool
+nsContentUtils::IsFileImage(nsIFile* aFile, nsACString& aType)
+{
+  nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
+  if (!mime) {
+    return false;
+  }
+
+  nsresult rv = mime->GetTypeFromFile(aFile, aType);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  return StringBeginsWith(aType, NS_LITERAL_CSTRING("image/"));
 }
 
 void
 nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable,
                                               IPCDataTransfer* aIPCDataTransfer,
+                                              bool aInSyncMessage,
                                               mozilla::dom::nsIContentChild* aChild,
                                               mozilla::dom::nsIContentParent* aParent)
 {
   MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
 
   if (aTransferable) {
     nsCOMPtr<nsISupportsArray> flavorList;
     aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
@@ -7460,17 +7518,17 @@ nsContentUtils::TransferableToIPCTransfe
                               imgIContainer::FLAG_SYNC_DECODE);
             if (surface) {
               RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
                 surface->GetDataSurface();
               size_t length;
               int32_t stride;
               mozilla::UniquePtr<char[]> surfaceData =
                 nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
-              
+
               IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
               item->flavor() = nsCString(flavorStr);
               item->data() = nsCString(surfaceData.get(), length);
 
               IPCDataTransferImage& imageDetails = item->imageDetails();
               mozilla::gfx::IntSize size = dataSurface->GetSize();
               imageDetails.width() = size.width;
               imageDetails.height() = size.height;
@@ -7480,24 +7538,44 @@ nsContentUtils::TransferableToIPCTransfe
 
             continue;
           }
 
           // Otherwise, handle this as a file.
           nsCOMPtr<BlobImpl> blobImpl;
           nsCOMPtr<nsIFile> file = do_QueryInterface(data);
           if (file) {
+            // If we can send this over as a blob, do so. Otherwise, we're
+            // responding to a sync message and the child can't process the blob
+            // constructor before processing our response, which would crash. In
+            // that case, hope that the caller is nsClipboardProxy::GetData,
+            // called from editor and send over images as raw data.
+            if (aInSyncMessage) {
+              nsAutoCString type;
+              if (IsFileImage(file, type)) {
+                IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+                item->flavor() = type;
+                SlurpFileToString(file, item->data());
+              }
+
+              continue;
+            }
+
             blobImpl = new BlobImplFile(file, false);
             ErrorResult rv;
             // Ensure that file data is cached no that the content process
             // has this data available to it when passed over:
             blobImpl->GetSize(rv);
             blobImpl->GetLastModified(rv);
             blobImpl->LookupAndCacheIsDirectory();
           } else {
+            if (aInSyncMessage) {
+              // Can't do anything.
+              continue;
+            }
             blobImpl = do_QueryInterface(data);
           }
           if (blobImpl) {
             IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
             item->flavor() = nsCString(flavorStr);
             if (aChild) {
               item->data() =
                 mozilla::dom::BlobChild::GetOrCreate(aChild,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2403,23 +2403,40 @@ public:
   /*
    * Call the given callback on all remote children of the given top-level
    * window.
    */
   static void CallOnAllRemoteChildren(nsIDOMWindow* aWindow,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
+  /**
+   * Given an nsIFile, attempts to read it into aString.
+   *
+   * Note: Use sparingly! This causes main-thread I/O, which causes jank and all
+   * other bad things.
+   */
+  static nsresult SlurpFileToString(nsIFile* aFile, nsACString& aString);
+
+  /**
+   * Returns true if the mime service thinks this file contains an image.
+   *
+   * The content type is returned in aType.
+   */
+  static bool IsFileImage(nsIFile* aFile, nsACString& aType);
+
   static void TransferablesToIPCTransferables(nsISupportsArray* aTransferables,
                                               nsTArray<mozilla::dom::IPCDataTransfer>& aIPC,
+                                              bool aInSyncMessage,
                                               mozilla::dom::nsIContentChild* aChild,
                                               mozilla::dom::nsIContentParent* aParent);
 
   static void TransferableToIPCTransferable(nsITransferable* aTransferable,
                                             mozilla::dom::IPCDataTransfer* aIPCDataTransfer,
+                                            bool aInSyncMessage,
                                             mozilla::dom::nsIContentChild* aChild,
                                             mozilla::dom::nsIContentParent* aParent);
 
   /*
    * Get the pixel data from the given source surface and return it as a buffer.
    * The length and stride will be assigned from the surface.
    */
   static mozilla::UniquePtr<char[]> GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -21,16 +21,17 @@ support-files =
   fileconstructor_file.png
   frame_bug814638.xul
   frame_registerElement_content.html
   registerElement_ep.js
   host_bug814638.xul
   window_nsITextInputProcessor.xul
   title_window.xul
 
+[test_bug120684.xul]
 [test_bug206691.xul]
 [test_bug339494.xul]
 [test_bug357450.xul]
 [test_bug380418.html]
 [test_bug380418.html^headers^]
 [test_bug383430.html]
 [test_bug391728.html]
 [test_bug418986-1.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_bug120684.xul
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=120684
+-->
+<window title="Mozilla Bug 120684"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=120684"
+     target="_blank">Mozilla Bug 120684</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 120684 **/
+
+  var list = new ChromeNodeList();
+  is(list.length, 0, "Length should be initially 0.");
+
+  ok(list instanceof NodeList, "ChromeNodeList object should be an instance of NodeList.");
+
+  try {
+    list.append(document);
+    ok(false, "should have throw!");
+  } catch(ex) {
+    ok(true, "ChromeNodeList supports only nsIContent objects for now.");
+  }
+
+  try {
+    list.remove(document);
+    ok(false, "should have throw!");
+  } catch(ex) {
+    ok(true, "ChromeNodeList supports only nsIContent objects for now.");
+  }
+  is(list.length, 0, "Length should be 0.");
+
+  list.append(document.documentElement);
+  is(list.length, 1, "Length should be 1.");
+  is(list[0], document.documentElement);
+  is(list[1], undefined);
+
+  // Removing element which isn't in the list shouldn't do anything.
+  list.remove(document.createElement("foo"));
+  is(list.length, 1, "Length should be 1.");
+  is(list[0], document.documentElement);
+
+  list.remove(document.documentElement);
+  is(list.length, 0, "Length should be 0.");
+  is(list[0], undefined);
+
+  var e1 = document.createElement("foo");
+  var e2 = document.createElement("foo");
+  var e3 = document.createElement("foo");
+
+  list.append(e1);
+  list.append(e2);
+  list.append(e3);
+
+  is(list[0], e1);
+  is(list[1], e2);
+  is(list[2], e3);
+  is(list.length, 3);
+
+  list.remove(e2);
+  is(list[0], e1);
+  is(list[1], e3);
+  is(list[2], undefined);
+  is(list.length, 2);
+
+  // A leak test.
+  list.expando = list;
+
+  ]]>
+  </script>
+</window>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -532,54 +532,29 @@ DefineUnforgeableAttributes(JSContext* c
                             const Prefable<const JSPropertySpec>* props)
 {
   return DefinePrefable(cx, obj, props);
 }
 
 
 // We should use JSFunction objects for interface objects, but we need a custom
 // hasInstance hook because we have new interface objects on prototype chains of
-// old (XPConnect-based) bindings. Because Function.prototype.toString throws if
-// passed a non-Function object we also need to provide our own toString method
-// for interface objects.
-
-static bool
-InterfaceObjectToString(JSContext* cx, unsigned argc, JS::Value *vp)
+// old (XPConnect-based) bindings. We also need Xrays and arbitrary numbers of
+// reserved slots (e.g. for named constructors).  So we define a custom
+// funToString ObjectOps member for interface objects.
+JSString*
+InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
+                        unsigned /* indent */)
 {
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  if (!args.thisv().isObject()) {
-    JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr,
-                         JSMSG_CANT_CONVERT_TO, "null", "object");
-    return false;
-  }
-
-  JS::Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
-  JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(thisObj, /* stopAtOuter = */ false));
-  if (!obj) {
-    JS_ReportError(cx, "Permission denied to access object");
-    return false;
-  }
-
-  const js::Class* clasp = js::GetObjectClass(obj);
-  if (!IsDOMIfaceAndProtoClass(clasp)) {
-    JS_ReportError(cx, "toString called on incompatible object");
-    return false;
-  }
+  const js::Class* clasp = js::GetObjectClass(aObject);
+  MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
 
   const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
     DOMIfaceAndProtoJSClass::FromJSClass(clasp);
-  JS::Rooted<JSString*> str(cx,
-                            JS_NewStringCopyZ(cx,
-                                              ifaceAndProtoJSClass->mToString));
-  if (!str) {
-    return false;
-  }
-
-  args.rval().setString(str);
-  return true;
+  return JS_NewStringCopyZ(aCx, ifaceAndProtoJSClass->mToString);
 }
 
 bool
 Constructor(JSContext* cx, unsigned argc, JS::Value* vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JS::Value& v =
     js::GetFunctionNativeReserved(&args.callee(),
@@ -642,25 +617,16 @@ CreateInterfaceObject(JSContext* cx, JS:
     constructor = CreateConstructor(cx, global, name, constructorNative,
                                     ctorNargs);
   }
   if (!constructor) {
     return nullptr;
   }
 
   if (constructorClass) {
-    // Have to shadow Function.prototype.toString, since that throws
-    // on things that are not js::FunctionClass.
-    JS::Rooted<JSFunction*> toString(cx,
-      JS_DefineFunction(cx, constructor, "toString", InterfaceObjectToString,
-                        0, 0));
-    if (!toString) {
-      return nullptr;
-    }
-
     if (!JS_DefineProperty(cx, constructor, "length", ctorNargs,
                            JSPROP_READONLY)) {
       return nullptr;
     }
 
     // Might as well intern, since we're going to need an atomized
     // version of name anyway when we stick our constructor on the
     // global.
@@ -1565,32 +1531,16 @@ XrayResolveOwnProperty(JSContext* cx, JS
   } else if (type == eInterface) {
     if (IdEquals(id, "prototype")) {
       return nativePropertyHooks->mPrototypeID == prototypes::id::_ID_Count ||
              ResolvePrototypeOrConstructor(cx, wrapper, obj,
                                            nativePropertyHooks->mPrototypeID,
                                            JSPROP_PERMANENT | JSPROP_READONLY,
                                            desc, cacheOnHolder);
     }
-
-    if (IdEquals(id, "toString") && !JS_ObjectIsFunction(cx, obj)) {
-      MOZ_ASSERT(IsDOMIfaceAndProtoClass(js::GetObjectClass(obj)));
-
-      JS::Rooted<JSFunction*> toString(cx, JS_NewFunction(cx, InterfaceObjectToString, 0, 0, "toString"));
-      if (!toString) {
-        return false;
-      }
-
-      cacheOnHolder = true;
-
-      FillPropertyDescriptor(desc, wrapper, 0,
-                             JS::ObjectValue(*JS_GetFunctionObject(toString)));
-
-      return JS_WrapPropertyDescriptor(cx, desc);
-    }
   } else {
     MOZ_ASSERT(IsInterfacePrototype(type));
 
     if (IdEquals(id, "constructor")) {
       return nativePropertyHooks->mConstructorID == constructors::id::_ID_Count ||
              ResolvePrototypeOrConstructor(cx, wrapper, obj,
                                            nativePropertyHooks->mConstructorID,
                                            0, desc, cacheOnHolder);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -3354,12 +3354,16 @@ void
 SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject,
                              UseCounter aUseCounter);
 
 // Warnings
 void
 DeprecationWarning(JSContext* aCx, JSObject* aObject,
                    nsIDocument::DeprecatedOperations aOperation);
 
+// A callback to perform funToString on an interface object
+JSString*
+InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
+                        unsigned /* indent */);
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -448,17 +448,18 @@ class CGDOMJSClass(CGThing):
                       nullptr, /* getProperty */
                       nullptr, /* setProperty */
                       nullptr, /* getOwnPropertyDescriptor */
                       nullptr, /* deleteProperty */
                       nullptr, /* watch */
                       nullptr, /* unwatch */
                       nullptr, /* getElements */
                       nullptr, /* enumerate */
-                      mozilla::dom::ObjectToOuterObjectValue /* thisValue */
+                      mozilla::dom::ObjectToOuterObjectValue, /* thisValue */
+                      nullptr, /* funToString */
                     }
                     """,
                     objectMoved=objectMovedHook)
         else:
             classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
             reservedSlots = slotCount
         if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
             resolveHook = RESOLVE_HOOK_NAME
@@ -725,17 +726,31 @@ class CGInterfaceObjectJSClass(CGThing):
                 nullptr,               /* mayResolve */
                 nullptr,               /* finalize */
                 ${ctorname}, /* call */
                 ${hasInstance}, /* hasInstance */
                 ${ctorname}, /* construct */
                 nullptr,               /* trace */
                 JS_NULL_CLASS_SPEC,
                 JS_NULL_CLASS_EXT,
-                JS_NULL_OBJECT_OPS
+                {
+                  nullptr, /* lookupProperty */
+                  nullptr, /* defineProperty */
+                  nullptr, /* hasProperty */
+                  nullptr, /* getProperty */
+                  nullptr, /* setProperty */
+                  nullptr, /* getOwnPropertyDescriptor */
+                  nullptr, /* deleteProperty */
+                  nullptr, /* watch */
+                  nullptr, /* unwatch */
+                  nullptr, /* getElements */
+                  nullptr, /* enumerate */
+                  nullptr, /* thisObject */
+                  InterfaceObjectToString, /* funToString */
+                }
               },
               eInterface,
               ${hooks},
               "function ${name}() {\\n    [native code]\\n}",
               ${prototypeID},
               ${depth},
               ${protoGetter}
             };
--- a/dom/bindings/test/test_interfaceToString.html
+++ b/dom/bindings/test/test_interfaceToString.html
@@ -26,13 +26,22 @@ try {
     is(eventTargetToString, nativeToString,
        "Stringifying a DOM interface object should return the same string" +
        "as stringifying a native function.");
 }
 catch (e) {
     ok(false, "Stringifying a DOM interface object shouldn't throw.");
 }
 
+try {
+    eventTargetToString = Function.prototype.toString.call(EventTarget);
+    is(eventTargetToString, nativeToString,
+       "Stringifying a DOM interface object via Function.prototype.toString " +
+       "should return the same string as stringifying a native function.");
+}
+catch (e) {
+    ok(false, "Stringifying a DOM interface object shouldn't throw.");
+}
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -344,19 +344,23 @@ TypeUtils::ToInternalRequest(const Cache
   internalRequest->SetCredentialsMode(aIn.credentials());
   internalRequest->SetContentPolicyType(aIn.contentPolicyType());
   internalRequest->SetCacheMode(aIn.requestCache());
   internalRequest->SetRedirectMode(aIn.requestRedirect());
 
   RefPtr<InternalHeaders> internalHeaders =
     ToInternalHeaders(aIn.headers(), aIn.headersGuard());
   ErrorResult result;
-  internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
+
+  // Be careful to fill the headers before setting the guard in order to
+  // correctly re-create the original headers.
+  internalRequest->Headers()->Fill(*internalHeaders, result);
   MOZ_ASSERT(!result.Failed());
-  internalRequest->Headers()->Fill(*internalHeaders, result);
+
+  internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
   MOZ_ASSERT(!result.Failed());
 
   nsCOMPtr<nsIInputStream> stream = ReadStream::Create(aIn.body());
 
   internalRequest->SetBody(stream);
 
   return internalRequest.forget();
 }
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -298,24 +298,34 @@ FetchDriver::HttpFetch()
     nsAutoCString method;
     mRequest->GetMethod(method);
     rv = httpChan->SetRequestMethod(method);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Set the same headers.
     nsAutoTArray<InternalHeaders::Entry, 5> headers;
     mRequest->Headers()->GetEntries(headers);
+    bool hasAccept = false;
     for (uint32_t i = 0; i < headers.Length(); ++i) {
+      if (!hasAccept && headers[i].mName.EqualsLiteral("accept")) {
+        hasAccept = true;
+      }
       if (headers[i].mValue.IsEmpty()) {
         httpChan->SetEmptyRequestHeader(headers[i].mName);
       } else {
         httpChan->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */);
       }
     }
 
+    if (!hasAccept) {
+      httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
+                                 NS_LITERAL_CSTRING("*/*"),
+                                 false /* merge */);
+    }
+
     // Step 2. Set the referrer.
     nsAutoString referrer;
     mRequest->GetReferrer(referrer);
     if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
       rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
                                                          mDocument,
                                                          httpChan);
       NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -146,23 +146,18 @@ void
 InternalHeaders::Clear()
 {
   mList.Clear();
 }
 
 void
 InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv)
 {
-  // Rather than re-validate all current headers, just require code to set
-  // this prior to populating the InternalHeaders object.  Allow setting immutable
-  // late, though, as that is pretty much required to have a  useful, immutable
-  // headers object.
-  if (aGuard != HeadersGuardEnum::Immutable && mList.Length() > 0) {
-    aRv.Throw(NS_ERROR_FAILURE);
-  }
+  // The guard is only checked during ::Set() and ::Append() in the spec.  It
+  // does not require revalidating headers already set.
   mGuard = aGuard;
 }
 
 InternalHeaders::~InternalHeaders()
 {
 }
 
 // static
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1399,21 +1399,21 @@ HTMLMediaElement::CurrentTime() const
 {
   if (MediaStream* stream = GetSrcMediaStream()) {
     if (mSrcStreamPausedCurrentTime >= 0) {
       return mSrcStreamPausedCurrentTime;
     }
     return stream->StreamTimeToSeconds(stream->GetCurrentTime());
   }
 
-  if (mDecoder) {
+  if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
     return mDecoder->GetCurrentTime();
   }
 
-  return 0.0;
+  return mDefaultPlaybackStartPosition;
 }
 
 NS_IMETHODIMP HTMLMediaElement::GetCurrentTime(double* aCurrentTime)
 {
   *aCurrentTime = CurrentTime();
   return NS_OK;
 }
 
@@ -1479,49 +1479,40 @@ HTMLMediaElement::Seek(double aTime,
   // play will not be blocked when initiated by a script.
   if (EventStateManager::IsHandlingUserInput() || nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
     mHasUserInteraction = true;
   }
 
   StopSuspendingAfterFirstFrame();
 
   if (mSrcStream) {
-    // do nothing since streams aren't seekable; we effectively clamp to
-    // the current time.
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    // do nothing since media streams have an empty Seekable range.
     return;
   }
 
-  if (!mPlayed) {
-    LOG(LogLevel::Debug, ("HTMLMediaElement::mPlayed not available."));
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-
-  if (mCurrentPlayRangeStart != -1.0) {
+  if (mPlayed && mCurrentPlayRangeStart != -1.0) {
     double rangeEndTime = CurrentTime();
     LOG(LogLevel::Debug, ("%p Adding \'played\' a range : [%f, %f]", this, mCurrentPlayRangeStart, rangeEndTime));
     // Multiple seek without playing, or seek while playing.
     if (mCurrentPlayRangeStart != rangeEndTime) {
       mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
     }
     // Reset the current played range start time. We'll re-set it once
     // the seek completes.
     mCurrentPlayRangeStart = -1.0;
   }
 
-  if (!mDecoder) {
-    LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) failed: no decoder", this, aTime));
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+    mDefaultPlaybackStartPosition = aTime;
     return;
   }
 
-  if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
-    LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) failed: no source", this, aTime));
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  if (!mDecoder) {
+    // mDecoder must always be set in order to reach this point.
+    NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
     return;
   }
 
   // Clamp the seek target to inside the seekable ranges.
   RefPtr<dom::TimeRanges> seekable = new dom::TimeRanges(ToSupports(OwnerDoc()));
   media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
   if (seekableIntervals.IsInvalid()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@@ -2109,17 +2100,18 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
     mAudioChannelVolume(1.0),
     mPlayingThroughTheAudioChannel(false),
     mDisableVideo(false),
     mPlayBlockedBecauseHidden(false),
     mMediaStreamTrackListener(nullptr),
     mElementInTreeState(ELEMENT_NOT_INTREE),
     mHasUserInteraction(false),
-    mFirstFrameLoaded(false)
+    mFirstFrameLoaded(false),
+    mDefaultPlaybackStartPosition(0.0)
 {
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
 
@@ -3435,16 +3427,21 @@ void HTMLMediaElement::MetadataLoaded(co
   } else {
     mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   }
 
   if (IsVideo() && aInfo->HasVideo()) {
     // We are a video element playing video so update the screen wakelock
     NotifyOwnerDocumentActivityChangedInternal();
   }
+
+  if (mDefaultPlaybackStartPosition > 0) {
+    SetCurrentTime(mDefaultPlaybackStartPosition);
+    mDefaultPlaybackStartPosition = 0.0;
+  }
 }
 
 void HTMLMediaElement::FirstFrameLoaded()
 {
   NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
 
   if (!mFirstFrameLoaded) {
     mFirstFrameLoaded = true;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1511,14 +1511,19 @@ private:
   TimeDurationAccumulator mJoinLatency;
 
   // Indicates if user has interacted with the element.
   // Used to block autoplay when disabled.
   bool mHasUserInteraction;
 
   // True if the first frame has been successfully loaded.
   bool mFirstFrameLoaded;
+
+  // Media elements also have a default playback start position, which must
+  // initially be set to zero seconds. This time is used to allow the element to
+  // be seeked even before the media is loaded.
+  double mDefaultPlaybackStartPosition;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h
--- a/dom/html/MediaDocument.cpp
+++ b/dom/html/MediaDocument.cpp
@@ -54,17 +54,17 @@ MediaDocumentStreamListener::OnStartRequ
   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
 
   mDocument->StartLayout();
 
   if (mNextStream) {
     return mNextStream->OnStartRequest(request, ctxt);
   }
 
-  return NS_BINDING_ABORTED;
+  return NS_ERROR_PARSED_DATA_CACHED;
 }
 
 NS_IMETHODIMP
 MediaDocumentStreamListener::OnStopRequest(nsIRequest* request,
                                            nsISupports *ctxt,
                                            nsresult status)
 {
   nsresult rv = NS_OK;
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -1981,31 +1981,31 @@ private:
     const uint8_t* uncompressed;
     uint32_t uncompressedLength;
     rv = aArguments->GetSharedBlob(0, &uncompressedLength, &uncompressed);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
-    nsAutoArrayPtr<char> compressed(new (fallible) char[compressedLength]);
+    UniqueFreePtr<uint8_t> compressed(
+      static_cast<uint8_t*>(malloc(compressedLength)));
     if (NS_WARN_IF(!compressed)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     snappy::RawCompress(reinterpret_cast<const char*>(uncompressed),
-                        uncompressedLength, compressed.get(),
+                        uncompressedLength,
+                        reinterpret_cast<char*>(compressed.get()),
                         &compressedLength);
 
-    std::pair<const void *, int> data(static_cast<void*>(compressed.get()),
-                                      int(compressedLength));
-
-    // XXX This copies the buffer again... There doesn't appear to be any way to
-    //     preallocate space and write directly to a BlobVariant at the moment.
-    nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
+    std::pair<uint8_t *, int> data(compressed.release(),
+                                   int(compressedLength));
+
+    nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
 
     result.forget(aResult);
     return NS_OK;
   }
 };
 
 nsresult
 UpgradeSchemaFrom8To9_0(mozIStorageConnection* aConnection)
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2823,17 +2823,17 @@ ContentParent::RecvGetClipboard(nsTArray
     trans->Init(nullptr);
 
     for (uint32_t t = 0; t < aTypes.Length(); t++) {
       trans->AddDataFlavor(aTypes[t].get());
     }
 
     clipboard->GetData(trans, aWhichClipboard);
     nsContentUtils::TransferableToIPCTransferable(trans, aDataTransfer,
-                                                  nullptr, this);
+                                                  true, nullptr, this);
     return true;
 }
 
 bool
 ContentParent::RecvEmptyClipboard(const int32_t& aWhichClipboard)
 {
     nsresult rv;
     nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
@@ -5267,16 +5267,17 @@ ContentParent::MaybeInvokeDragSession(Ta
       // needed.
       transfer->FillAllExternalData();
       nsCOMPtr<nsILoadContext> lc = aParent ?
                                      aParent->GetLoadContext() : nullptr;
       nsCOMPtr<nsISupportsArray> transferables =
         transfer->GetTransferables(lc);
       nsContentUtils::TransferablesToIPCTransferables(transferables,
                                                       dataTransfers,
+                                                      false,
                                                       nullptr,
                                                       this);
       uint32_t action;
       session->GetDragAction(&action);
       mozilla::unused << SendInvokeDragSession(dataTransfers, action);
     }
   }
 }
--- a/dom/media/mediasource/test/test_SeekNoData_mp4.html
+++ b/dom/media/mediasource/test/test_SeekNoData_mp4.html
@@ -18,23 +18,37 @@ function fuzzyEquals(a, b) {
 }
 
 runWithMSE(function(ms, el) {
   el.controls = true;
   once(ms, 'sourceopen').then(function() {
     ok(true, "Receive a sourceopen event");
     var audiosb = ms.addSourceBuffer("audio/mp4");
     var videosb = ms.addSourceBuffer("video/mp4");
+    el.addEventListener("error", function(e) {
+      ok(false, "should not fire '" + e + "' event");
+    });
+    is(el.readyState, el.HAVE_NOTHING, "readyState is HAVE_NOTHING");
+    try {
+      el.currentTime = 3;
+    } catch (e) {
+      ok(false, "should not throw '" + e + "' exception");
+    }
+    is(el.currentTime, 3, "currentTime is default playback start position");
+    is(el.seeking, false, "seek not started with HAVE_NOTHING");
     fetchAndLoad(audiosb, 'bipbop/bipbop_audio', ['init'], '.mp4')
     .then(fetchAndLoad.bind(null, videosb, 'bipbop/bipbop_video', ['init'], '.mp4'))
     .then(once.bind(null, el, 'loadedmetadata'))
     .then(function() {
       var p = once(el, 'seeking');
       el.play();
       el.currentTime = 5;
+      is(el.readyState, el.HAVE_METADATA, "readyState is HAVE_METADATA");
+      is(el.seeking, true, "seek not started with HAVE_METADATA");
+      is(el.currentTime, 5, "currentTime is seek position");
       return p;
     })
     .then(function() {
        ok(true, "Got seeking event");
        var promises = [];
        promises.push(once(el, 'seeked'));
        promises.push(fetchAndLoad(audiosb, 'bipbop/bipbop_audio', range(5, 9), '.m4s'));
        promises.push(fetchAndLoad(videosb, 'bipbop/bipbop_video', range(6, 10), '.m4s'));
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -129,28 +129,61 @@ PDMFactory::~PDMFactory()
 
 already_AddRefed<MediaDataDecoder>
 PDMFactory::CreateDecoder(const TrackInfo& aConfig,
                           FlushableTaskQueue* aTaskQueue,
                           MediaDataDecoderCallback* aCallback,
                           layers::LayersBackend aLayersBackend,
                           layers::ImageContainer* aImageContainer)
 {
-  RefPtr<PlatformDecoderModule> current = (mEMEPDM && aConfig.mCrypto.mValid)
-    ? mEMEPDM : GetDecoder(aConfig.mMimeType);
+  bool isEncrypted = mEMEPDM && aConfig.mCrypto.mValid;
+
+  if (isEncrypted) {
+    return CreateDecoderWithPDM(mEMEPDM,
+                                aConfig,
+                                aTaskQueue,
+                                aCallback,
+                                aLayersBackend,
+                                aImageContainer);
+  }
 
-  if (!current) {
-    return nullptr;
+  for (auto& current : mCurrentPDMs) {
+    if (!current->SupportsMimeType(aConfig.mMimeType)) {
+      continue;
+    }
+    RefPtr<MediaDataDecoder> m =
+      CreateDecoderWithPDM(current,
+                           aConfig,
+                           aTaskQueue,
+                           aCallback,
+                           aLayersBackend,
+                           aImageContainer);
+    if (m) {
+      return m.forget();
+    }
   }
+  NS_WARNING("Unable to create a decoder, no platform found.");
+  return nullptr;
+}
+
+already_AddRefed<MediaDataDecoder>
+PDMFactory::CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
+                                 const TrackInfo& aConfig,
+                                 FlushableTaskQueue* aTaskQueue,
+                                 MediaDataDecoderCallback* aCallback,
+                                 layers::LayersBackend aLayersBackend,
+                                 layers::ImageContainer* aImageContainer)
+{
+  MOZ_ASSERT(aPDM);
   RefPtr<MediaDataDecoder> m;
 
   if (aConfig.GetAsAudioInfo()) {
-    m = current->CreateAudioDecoder(*aConfig.GetAsAudioInfo(),
-                                    aTaskQueue,
-                                    aCallback);
+    m = aPDM->CreateAudioDecoder(*aConfig.GetAsAudioInfo(),
+                                 aTaskQueue,
+                                 aCallback);
     return m.forget();
   }
 
   if (!aConfig.GetAsVideoInfo()) {
     return nullptr;
   }
 
   MediaDataDecoderCallback* callback = aCallback;
@@ -160,35 +193,35 @@ PDMFactory::CreateDecoder(const TrackInf
     callbackWrapper->SetVideoOutputMinimumInterval(
       TimeDuration::FromMilliseconds(sVideoOutputMinimumInterval_ms));
     callbackWrapper->SetDontDelayInputExhausted(sDontDelayInputExhausted);
     callback = callbackWrapper.get();
   }
 
   if (H264Converter::IsH264(aConfig)) {
     RefPtr<H264Converter> h
-      = new H264Converter(current,
+      = new H264Converter(aPDM,
                           *aConfig.GetAsVideoInfo(),
                           aLayersBackend,
                           aImageContainer,
                           aTaskQueue,
                           callback);
     const nsresult rv = h->GetLastError();
     if (NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_INITIALIZED) {
       // The H264Converter either successfully created the wrapped decoder,
       // or there wasn't enough AVCC data to do so. Otherwise, there was some
       // problem, for example WMF DLLs were missing.
       m = h.forget();
     }
   } else {
-    m = current->CreateVideoDecoder(*aConfig.GetAsVideoInfo(),
-                                    aLayersBackend,
-                                    aImageContainer,
-                                    aTaskQueue,
-                                    callback);
+    m = aPDM->CreateVideoDecoder(*aConfig.GetAsVideoInfo(),
+                                 aLayersBackend,
+                                 aImageContainer,
+                                 aTaskQueue,
+                                 callback);
   }
 
   if (callbackWrapper && m) {
     m = new DecoderFuzzingWrapper(m.forget(), callbackWrapper.forget());
   }
 
   return m.forget();
 }
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -48,16 +48,23 @@ public:
 
 private:
   virtual ~PDMFactory();
   void CreatePDMs();
   // Startup the provided PDM and add it to our list if successful.
   bool StartupPDM(PlatformDecoderModule* aPDM);
   // Returns the first PDM in our list supporting the mimetype.
   already_AddRefed<PlatformDecoderModule> GetDecoder(const nsACString& aMimeType);
+  already_AddRefed<MediaDataDecoder>
+  CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
+                       const TrackInfo& aConfig,
+                       FlushableTaskQueue* aTaskQueue,
+                       MediaDataDecoderCallback* aCallback,
+                       layers::LayersBackend aLayersBackend,
+                       layers::ImageContainer* aImageContainer);
 
   // PDM pref caches...
   static bool sUseBlankDecoder;
 #ifdef MOZ_GONK_MEDIACODEC
   static bool sGonkDecoderEnabled;
 #endif
 #ifdef MOZ_WIDGET_ANDROID
   static bool sAndroidMCDecoderPreferred;
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -209,16 +209,18 @@ public:
   // Decoder needs to decide whether or not hardware accelearation is supported
   // after creating. It doesn't need to call Init() before calling this function.
   virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const { return false; }
 
   // ConfigurationChanged will be called to inform the video or audio decoder
   // that the format of the next input sample is about to change.
   // If video decoder, aConfig will be a VideoInfo object.
   // If audio decoder, aConfig will be a AudioInfo object.
+  // It is not safe to store a reference to this object and the decoder must
+  // make a copy.
   virtual nsresult ConfigurationChanged(const TrackInfo& aConfig)
   {
     return NS_OK;
   }
 };
 
 } // namespace mozilla
 
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -242,32 +242,40 @@ public:
 
     int32_t sampleRate;
     NS_ENSURE_SUCCESS(rv =
         aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &sampleRate), rv);
 
     int32_t size;
     NS_ENSURE_SUCCESS(rv = aInfo->Size(&size), rv);
 
-    const int32_t numFrames = (size / numChannels) / 2;
-    AudioDataValue* audio = new AudioDataValue[size];
-    PodCopy(audio, static_cast<AudioDataValue*>(aBuffer), size);
-
     int32_t offset;
     NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv);
 
+#ifdef MOZ_SAMPLE_TYPE_S16
+    int32_t numSamples = size / 2;
+#else
+#error We only support 16-bit integer PCM
+#endif
+
+    const int32_t numFrames = numSamples / numChannels;
+    AudioDataValue* audio = new AudioDataValue[numSamples];
+
+    uint8_t* bufferStart = static_cast<uint8_t*>(aBuffer) + offset;
+    PodCopy(audio, reinterpret_cast<AudioDataValue*>(bufferStart), numSamples);
+
     int64_t presentationTimeUs;
     NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv);
 
-    RefPtr<AudioData> data = new AudioData(offset, presentationTimeUs,
-                                             aDuration.ToMicroseconds(),
-                                             numFrames,
-                                             audio,
-                                             numChannels,
-                                             sampleRate);
+    RefPtr<AudioData> data = new AudioData(0, presentationTimeUs,
+                                           aDuration.ToMicroseconds(),
+                                           numFrames,
+                                           audio,
+                                           numChannels,
+                                           sampleRate);
     INVOKE_CALLBACK(Output, data);
     return NS_OK;
   }
 };
 
 
 bool AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType)
 {
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -63,17 +63,18 @@ SetNumOfDecoderThreads()
 }
 
 /* static */
 void
 WMFDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   sDXVAEnabled = gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding();
-  sIsIntelDecoderEnabled = Preferences::GetBool("media.webm.intel_decoder.enabled", false);
+  Preferences::AddBoolVarCache(&sIsIntelDecoderEnabled,
+                               "media.webm.intel_decoder.enabled");
   sLowLatencyMFTEnabled = Preferences::GetBool("media.wmf.low-latency.enabled", false);
   SetNumOfDecoderThreads();
 }
 
 /* static */
 int
 WMFDecoderModule::GetNumDecoderThreads()
 {
@@ -134,20 +135,23 @@ WMFDecoderModule::CreateAudioDecoder(con
 }
 
 static bool
 CanCreateMFTDecoder(const GUID& aGuid)
 {
   if (FAILED(wmf::MFStartup())) {
     return false;
   }
-  RefPtr<MFTDecoder> decoder(new MFTDecoder());
-  bool hasH264 = SUCCEEDED(decoder->Create(aGuid));
+  bool hasdecoder = false;
+  {
+    RefPtr<MFTDecoder> decoder(new MFTDecoder());
+    hasdecoder = SUCCEEDED(decoder->Create(aGuid));
+  }
   wmf::MFShutdown();
-  return hasH264;
+  return hasdecoder;
 }
 
 template<const GUID& aGuid>
 static bool
 CanCreateWMFDecoder()
 {
   static Maybe<bool> result;
   if (result.isNothing()) {
@@ -168,17 +172,17 @@ WMFDecoderModule::SupportsMimeType(const
        aMimeType.EqualsLiteral("video/mp4")) &&
       CanCreateWMFDecoder<CLSID_CMSH264DecoderMFT>()) {
     return true;
   }
   if (aMimeType.EqualsLiteral("audio/mpeg") &&
       CanCreateWMFDecoder<CLSID_CMP3DecMediaObject>()) {
     return true;
   }
-  if (sIsIntelDecoderEnabled) {
+  if (sIsIntelDecoderEnabled && sDXVAEnabled) {
     if (aMimeType.EqualsLiteral("video/webm; codecs=vp8") &&
         CanCreateWMFDecoder<CLSID_WebmMfVp8Dec>()) {
       return true;
     }
     if (aMimeType.EqualsLiteral("video/webm; codecs=vp9") &&
         CanCreateWMFDecoder<CLSID_WebmMfVp9Dec>()) {
       return true;
     }
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -227,9 +227,32 @@ WMFMediaDataDecoder::Drain()
 
 bool
 WMFMediaDataDecoder::IsHardwareAccelerated(nsACString& aFailureReason) const {
   MOZ_ASSERT(!mIsShutDown);
 
   return mMFTManager && mMFTManager->IsHardwareAccelerated(aFailureReason);
 }
 
+nsresult
+WMFMediaDataDecoder::ConfigurationChanged(const TrackInfo& aConfig)
+{
+  MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethodWithArg<UniquePtr<TrackInfo>&&>(
+    this,
+    &WMFMediaDataDecoder::ProcessConfigurationChanged,
+    aConfig.Clone());
+  mTaskQueue->Dispatch(runnable.forget());
+  return NS_OK;
+
+}
+
+void
+WMFMediaDataDecoder::ProcessConfigurationChanged(UniquePtr<TrackInfo>&& aConfig)
+{
+  if (mMFTManager) {
+    mMFTManager->ConfigurationChanged(*aConfig);
+  }
+}
+
 } // namespace mozilla
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.h
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.h
@@ -47,16 +47,18 @@ public:
 
   // Destroys all resources.
   virtual void Shutdown() = 0;
 
   virtual bool IsHardwareAccelerated(nsACString& aFailureReason) const { return false; }
 
   virtual TrackInfo::TrackType GetType() = 0;
 
+  virtual void ConfigurationChanged(const TrackInfo& aConfig) {}
+
 protected:
   // IMFTransform wrapper that performs the decoding.
   RefPtr<MFTDecoder> mDecoder;
 };
 
 // Decodes audio and video using Windows Media Foundation. Samples are decoded
 // using the MFTDecoder created by the MFTManager. This class implements
 // the higher-level logic that drives mapping the MFT to the async
@@ -76,16 +78,18 @@ public:
   nsresult Flush() override;
 
   nsresult Drain() override;
 
   nsresult Shutdown() override;
 
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
 
+  nsresult ConfigurationChanged(const TrackInfo& aConfig) override;
+
 private:
 
   // Called on the task queue. Inserts the sample into the decoder, and
   // extracts output if available.
   void ProcessDecode(MediaRawData* aSample);
 
   // Called on the task queue. Extracts output if available, and delivers
   // it to the reader. Called after ProcessDecode() and ProcessDrain().
@@ -96,16 +100,20 @@ private:
   void ProcessFlush();
 
   // Called on the task queue. Orders the MFT to drain, and then extracts
   // all available output.
   void ProcessDrain();
 
   void ProcessShutdown();
 
+  // Called on the task queue. Tell the MFT that the next Input will have a
+  // different configuration (typically resolution change).
+  void ProcessConfigurationChanged(UniquePtr<TrackInfo>&& aConfig);
+
   RefPtr<FlushableTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
 
   nsAutoPtr<MFTManager> mMFTManager;
 
   // The last offset into the media resource that was passed into Input().
   // This is used to approximate the decoder's position in the media resource.
   int64_t mLastStreamOffset;
--- a/dom/media/platforms/wmf/WMFUtils.cpp
+++ b/dom/media/platforms/wmf/WMFUtils.cpp
@@ -34,36 +34,31 @@ HNsToFrames(int64_t aHNs, uint32_t aRate
   i *= aRate;
   i /= HNS_PER_S;
   NS_ENSURE_TRUE(i.isValid(), E_FAIL);
   *aOutFrames = i.value();
   return S_OK;
 }
 
 HRESULT
-GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride)
+GetDefaultStride(IMFMediaType *aType, uint32_t aWidth, uint32_t* aOutStride)
 {
   // Try to get the default stride from the media type.
   HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, aOutStride);
   if (SUCCEEDED(hr)) {
     return S_OK;
   }
 
   // Stride attribute not set, calculate it.
   GUID subtype = GUID_NULL;
-  uint32_t width = 0;
-  uint32_t height = 0;
 
   hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
-  hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
-  hr = wmf::MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG*)(aOutStride));
+  hr = wmf::MFGetStrideForBitmapInfoHeader(subtype.Data1, aWidth, (LONG*)(aOutStride));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   return hr;
 }
 
 int32_t
 MFOffsetToInt32(const MFOffset& aOffset)
 {
--- a/dom/media/platforms/wmf/WMFUtils.h
+++ b/dom/media/platforms/wmf/WMFUtils.h
@@ -32,17 +32,17 @@ inline int64_t
 HNsToUsecs(int64_t hNanoSecs) {
   return hNanoSecs / 10;
 }
 
 HRESULT
 HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames);
 
 HRESULT
-GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride);
+GetDefaultStride(IMFMediaType *aType, uint32_t aWidth, uint32_t* aOutStride);
 
 int32_t
 MFOffsetToInt32(const MFOffset& aOffset);
 
 // Gets the sub-region of the video frame that should be displayed.
 // See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
 HRESULT
 GetPictureRegion(IMFMediaType* aMediaType, nsIntRect& aOutPictureRegion);
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -68,17 +68,19 @@ const CLSID CLSID_WebmMfVp9Dec =
 
 namespace mozilla {
 
 WMFVideoMFTManager::WMFVideoMFTManager(
                             const VideoInfo& aConfig,
                             mozilla::layers::LayersBackend aLayersBackend,
                             mozilla::layers::ImageContainer* aImageContainer,
                             bool aDXVAEnabled)
-  : mImageContainer(aImageContainer)
+  : mVideoInfo(aConfig)
+  , mVideoStride(0)
+  , mImageContainer(aImageContainer)
   , mDXVAEnabled(aDXVAEnabled)
   , mLayersBackend(aLayersBackend)
   // mVideoStride, mVideoWidth, mVideoHeight, mUseHwAccel are initialized in
   // Init().
 {
   MOZ_COUNT_CTOR(WMFVideoMFTManager);
 
   // Need additional checks/params to check vp8/vp9
@@ -247,23 +249,16 @@ WMFVideoMFTManager::InitInternal(bool aF
   }
 
   mDecoder = decoder;
   hr = SetDecoderMediaTypes();
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
   LOG("Video Decoder initialized, Using DXVA: %s", (mUseHwAccel ? "Yes" : "No"));
 
-  // Just in case ConfigureVideoFrameGeometry() does not set these
-  mVideoInfo = VideoInfo();
-  mVideoStride = 0;
-  mVideoWidth = 0;
-  mVideoHeight = 0;
-  mPictureRegion.SetEmpty();
-
   return true;
 }
 
 HRESULT
 WMFVideoMFTManager::SetDecoderMediaTypes()
 {
   // Setup the input/output media types.
   RefPtr<IMFMediaType> inputType;
@@ -367,59 +362,45 @@ WMFVideoMFTManager::ConfigureVideoFrameG
   // we use YV12, as that's easier for us to stick into our rendering
   // pipeline than NV12. NV12 has interleaved UV samples, whereas YV12
   // is a planar format.
   GUID videoFormat;
   hr = mediaType->GetGUID(MF_MT_SUBTYPE, &videoFormat);
   NS_ENSURE_TRUE(videoFormat == MFVideoFormat_NV12 || !mUseHwAccel, E_FAIL);
   NS_ENSURE_TRUE(videoFormat == MFVideoFormat_YV12 || mUseHwAccel, E_FAIL);
 
-  nsIntRect pictureRegion;
-  hr = GetPictureRegion(mediaType, pictureRegion);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
   UINT32 width = 0, height = 0;
   hr = MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, &width, &height);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
-  uint32_t aspectNum = 0, aspectDenom = 0;
-  hr = MFGetAttributeRatio(mediaType,
-                           MF_MT_PIXEL_ASPECT_RATIO,
-                           &aspectNum,
-                           &aspectDenom);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
+  mVideoInfo.mImage.width = width;
+  mVideoInfo.mImage.height = height;
+  nsIntRect pictureRegion = mVideoInfo.mImage;
   // Calculate and validate the picture region and frame dimensions after
   // scaling by the pixel aspect ratio.
   nsIntSize frameSize = nsIntSize(width, height);
-  nsIntSize displaySize = nsIntSize(pictureRegion.width, pictureRegion.height);
-  ScaleDisplayByAspectRatio(displaySize, float(aspectNum) / float(aspectDenom));
+  nsIntSize displaySize = nsIntSize(mVideoInfo.mDisplay.width, mVideoInfo.mDisplay.height);
   if (!IsValidVideoRegion(frameSize, pictureRegion, displaySize)) {
     // Video track's frame sizes will overflow. Ignore the video track.
     return E_FAIL;
   }
 
   if (mDXVA2Manager) {
     hr = mDXVA2Manager->ConfigureForSize(width, height);
     NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   }
 
   // Success! Save state.
-  mVideoInfo.mDisplay = displaySize;
-  GetDefaultStride(mediaType, &mVideoStride);
-  mVideoWidth = width;
-  mVideoHeight = height;
-  mPictureRegion = pictureRegion;
+  GetDefaultStride(mediaType, width, &mVideoStride);
 
-  LOG("WMFVideoMFTManager frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d) display=(%d,%d) PAR=%d:%d",
+  LOG("WMFVideoMFTManager frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d) display=(%d,%d)",
       width, height,
       mVideoStride,
-      mPictureRegion.x, mPictureRegion.y, mPictureRegion.width, mPictureRegion.height,
-      displaySize.width, displaySize.height,
-      aspectNum, aspectDenom);
+      pictureRegion.x, pictureRegion.y, pictureRegion.width, pictureRegion.height,
+      mVideoInfo.mDisplay.width, mVideoInfo.mDisplay.height);
 
   return S_OK;
 }
 
 HRESULT
 WMFVideoMFTManager::CreateBasicVideoFrame(IMFSample* aSample,
                                           int64_t aStreamOffset,
                                           VideoData** aOutVideoData)
@@ -451,35 +432,38 @@ WMFVideoMFTManager::CreateBasicVideoFram
     NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
     stride = mVideoStride;
   }
 
   // YV12, planar format: [YYYY....][VVVV....][UUUU....]
   // i.e., Y, then V, then U.
   VideoData::YCbCrBuffer b;
 
+  uint32_t videoWidth = mVideoInfo.mImage.width;
+  uint32_t videoHeight = mVideoInfo.mImage.height;
+
   // Y (Y') plane
   b.mPlanes[0].mData = data;
   b.mPlanes[0].mStride = stride;
-  b.mPlanes[0].mHeight = mVideoHeight;
-  b.mPlanes[0].mWidth = mVideoWidth;
+  b.mPlanes[0].mHeight = videoHeight;
+  b.mPlanes[0].mWidth = videoWidth;
   b.mPlanes[0].mOffset = 0;
   b.mPlanes[0].mSkip = 0;
 
   // The V and U planes are stored 16-row-aligned, so we need to add padding
   // to the row heights to ensure the Y'CbCr planes are referenced properly.
   uint32_t padding = 0;
-  if (mVideoHeight % 16 != 0) {
-    padding = 16 - (mVideoHeight % 16);
+  if (videoHeight % 16 != 0) {
+    padding = 16 - (videoHeight % 16);
   }
-  uint32_t y_size = stride * (mVideoHeight + padding);
-  uint32_t v_size = stride * (mVideoHeight + padding) / 4;
+  uint32_t y_size = stride * (videoHeight + padding);
+  uint32_t v_size = stride * (videoHeight + padding) / 4;
   uint32_t halfStride = (stride + 1) / 2;
-  uint32_t halfHeight = (mVideoHeight + 1) / 2;
-  uint32_t halfWidth = (mVideoWidth + 1) / 2;
+  uint32_t halfHeight = (videoHeight + 1) / 2;
+  uint32_t halfWidth = (videoWidth + 1) / 2;
 
   // U plane (Cb)
   b.mPlanes[1].mData = data + y_size + v_size;
   b.mPlanes[1].mStride = halfStride;
   b.mPlanes[1].mHeight = halfHeight;
   b.mPlanes[1].mWidth = halfWidth;
   b.mPlanes[1].mOffset = 0;
   b.mPlanes[1].mSkip = 0;
@@ -498,29 +482,29 @@ WMFVideoMFTManager::CreateBasicVideoFram
   NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
 
   RefPtr<layers::PlanarYCbCrImage> image =
     new IMFYCbCrImage(buffer, twoDBuffer);
 
   VideoData::SetVideoDataToImage(image,
                                  mVideoInfo,
                                  b,
-                                 mPictureRegion,
+                                 mVideoInfo.mImage,
                                  false);
 
   RefPtr<VideoData> v =
     VideoData::CreateFromImage(mVideoInfo,
                                mImageContainer,
                                aStreamOffset,
                                pts.ToMicroseconds(),
                                duration.ToMicroseconds(),
                                image.forget(),
                                false,
                                -1,
-                               mPictureRegion);
+                               mVideoInfo.mImage);
 
   v.forget(aOutVideoData);
   return S_OK;
 }
 
 HRESULT
 WMFVideoMFTManager::CreateD3DVideoFrame(IMFSample* aSample,
                                         int64_t aStreamOffset,
@@ -531,17 +515,17 @@ WMFVideoMFTManager::CreateD3DVideoFrame(
   NS_ENSURE_TRUE(mDXVA2Manager, E_ABORT);
   NS_ENSURE_TRUE(mUseHwAccel, E_ABORT);
 
   *aOutVideoData = nullptr;
   HRESULT hr;
 
   RefPtr<Image> image;
   hr = mDXVA2Manager->CopyToImage(aSample,
-                                  mPictureRegion,
+                                  mVideoInfo.mImage,
                                   mImageContainer,
                                   getter_AddRefs(image));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   NS_ENSURE_TRUE(image, E_FAIL);
 
   media::TimeUnit pts = GetSampleTime(aSample);
   NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
   media::TimeUnit duration = GetSampleDuration(aSample);
@@ -549,17 +533,17 @@ WMFVideoMFTManager::CreateD3DVideoFrame(
   RefPtr<VideoData> v = VideoData::CreateFromImage(mVideoInfo,
                                                      mImageContainer,
                                                      aStreamOffset,
                                                      pts.ToMicroseconds(),
                                                      duration.ToMicroseconds(),
                                                      image.forget(),
                                                      false,
                                                      -1,
-                                                     mPictureRegion);
+                                                     mVideoInfo.mImage);
 
   NS_ENSURE_TRUE(v, E_FAIL);
   v.forget(aOutVideoData);
 
   return S_OK;
 }
 
 // Blocks until decoded sample is produced by the deoder.
@@ -627,9 +611,16 @@ WMFVideoMFTManager::Shutdown()
 
 bool
 WMFVideoMFTManager::IsHardwareAccelerated(nsACString& aFailureReason) const
 {
   aFailureReason = mDXVAFailureReason;
   return mDecoder && mUseHwAccel;
 }
 
+void
+WMFVideoMFTManager::ConfigurationChanged(const TrackInfo& aConfig)
+{
+  MOZ_ASSERT(aConfig.GetAsVideoInfo());
+  mVideoInfo = *aConfig.GetAsVideoInfo();
+}
+
 } // namespace mozilla
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.h
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.h
@@ -34,16 +34,18 @@ public:
   void Shutdown() override;
 
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
 
   TrackInfo::TrackType GetType() override {
     return TrackInfo::kVideoTrack;
   }
 
+  void ConfigurationChanged(const TrackInfo& aConfig) override;
+
 private:
 
   bool InitializeDXVA(bool aForceD3D9);
 
   bool InitInternal(bool aForceD3D9);
 
   HRESULT ConfigureVideoFrameGeometry();
 
@@ -57,19 +59,16 @@ private:
 
   HRESULT SetDecoderMediaTypes();
 
   bool CanUseDXVA(IMFMediaType* aType);
 
   // Video frame geometry.
   VideoInfo mVideoInfo;
   uint32_t mVideoStride;
-  uint32_t mVideoWidth;
-  uint32_t mVideoHeight;
-  nsIntRect mPictureRegion;
 
   RefPtr<layers::ImageContainer> mImageContainer;
   nsAutoPtr<DXVA2Manager> mDXVA2Manager;
 
   RefPtr<IMFSample> mLastInput;
   float mLastDuration;
 
   bool mDXVAEnabled;
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -17,16 +17,17 @@ namespace mozilla
 
 H264Converter::H264Converter(PlatformDecoderModule* aPDM,
                              const VideoInfo& aConfig,
                              layers::LayersBackend aLayersBackend,
                              layers::ImageContainer* aImageContainer,
                              FlushableTaskQueue* aVideoTaskQueue,
                              MediaDataDecoderCallback* aCallback)
   : mPDM(aPDM)
+  , mOriginalConfig(aConfig)
   , mCurrentConfig(aConfig)
   , mLayersBackend(aLayersBackend)
   , mImageContainer(aImageContainer)
   , mVideoTaskQueue(aVideoTaskQueue)
   , mCallback(aCallback)
   , mDecoder(nullptr)
   , mNeedAVCC(aPDM->DecoderNeedsConversion(aConfig) == PlatformDecoderModule::kNeedAVCC)
   , mLastError(NS_OK)
@@ -48,24 +49,20 @@ H264Converter::Init()
   // We haven't been able to initialize a decoder due to a missing SPS/PPS.
   return MediaDataDecoder::InitPromise::CreateAndResolve(
            TrackType::kVideoTrack, __func__);
 }
 
 nsresult
 H264Converter::Input(MediaRawData* aSample)
 {
-  if (!mNeedAVCC) {
-    if (!mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample)) {
-      return NS_ERROR_FAILURE;
-    }
-  } else {
-    if (!mp4_demuxer::AnnexB::ConvertSampleToAVCC(aSample)) {
-      return NS_ERROR_FAILURE;
-    }
+  if (!mp4_demuxer::AnnexB::ConvertSampleToAVCC(aSample)) {
+    // We need AVCC content to be able to later parse the SPS.
+    // This is a no-op if the data is already AVCC.
+    return NS_ERROR_FAILURE;
   }
 
   if (mInitPromiseRequest.Exists()) {
     mMediaRawSamples.AppendElement(aSample);
     return NS_OK;
   }
 
   nsresult rv;
@@ -79,16 +76,21 @@ H264Converter::Input(MediaRawData* aSamp
       // Ignore for the time being, the MediaRawData will be dropped.
       return NS_OK;
     }
   } else {
     rv = CheckForSPSChange(aSample);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (!mNeedAVCC &&
+      !mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample)) {
+    return NS_ERROR_FAILURE;
+  }
+
   aSample->mExtraData = mCurrentConfig.mExtraData;
 
   return mDecoder->Input(aSample);
 }
 
 nsresult
 H264Converter::Flush()
 {
@@ -133,17 +135,17 @@ nsresult
 H264Converter::CreateDecoder()
 {
   if (mNeedAVCC && !mp4_demuxer::AnnexB::HasSPS(mCurrentConfig.mExtraData)) {
     // nothing found yet, will try again later
     return NS_ERROR_NOT_INITIALIZED;
   }
   UpdateConfigFromExtraData(mCurrentConfig.mExtraData);
 
-  mDecoder = mPDM->CreateVideoDecoder(mCurrentConfig,
+  mDecoder = mPDM->CreateVideoDecoder(mNeedAVCC ? mCurrentConfig : mOriginalConfig,
                                       mLayersBackend,
                                       mImageContainer,
                                       mVideoTaskQueue,
                                       mCallback);
   if (!mDecoder) {
     mLastError = NS_ERROR_FAILURE;
     return NS_ERROR_FAILURE;
   }
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -48,16 +48,17 @@ private:
   nsresult CreateDecoderAndInit(MediaRawData* aSample);
   nsresult CheckForSPSChange(MediaRawData* aSample);
   void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData);
 
   void OnDecoderInitDone(const TrackType aTrackType);
   void OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason);
 
   RefPtr<PlatformDecoderModule> mPDM;
+  const VideoInfo& mOriginalConfig;
   VideoInfo mCurrentConfig;
   layers::LayersBackend mLayersBackend;
   RefPtr<layers::ImageContainer> mImageContainer;
   RefPtr<FlushableTaskQueue> mVideoTaskQueue;
   nsTArray<RefPtr<MediaRawData>> mMediaRawSamples;
   MediaDataDecoderCallback* mCallback;
   RefPtr<MediaDataDecoder> mDecoder;
   MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -763,16 +763,17 @@ skip-if = (toolkit == 'android' && proce
 [test_replay_metadata.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_reset_events_async.html]
 [test_reset_src.html]
 [test_video_dimensions.html]
 tags=capturestream
 [test_resume.html]
 skip-if = true # bug 1021673
+[test_seek_nosrc.html]
 [test_seek_out_of_range.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_seek-1.html]
 skip-if = android_version == '10' # bug 1059116
 [test_seek-2.html]
 [test_seek-3.html]
 [test_seek-4.html]
 [test_seek-5.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_seek_nosrc.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media test: seek tests</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var SEEK_TIME = 3.5;
+var seekStarted = false;
+var seekCompleted = false;
+var metadata = false;
+
+var v = document.createElement('video');
+document.body.appendChild(v);
+SimpleTest.registerCleanupFunction(function () {
+  v.remove();
+});
+
+try {
+  v.currentTime = SEEK_TIME;
+} catch (e) {
+  ok(false, "should not fire '" + e + "' event");
+}
+is(v.readyState, v.HAVE_NOTHING, "readyState is HAVE_NOTHING");
+ok(!v.seeking, "can't be seeking prior src defined");
+is(v.currentTime, SEEK_TIME, "currentTime is default playback start position");
+once(v, "seeking", function() {
+  seekStarted = true;
+});
+once(v, "seeked", function() {
+  seekCompleted = true;
+});
+once(v, "loadedmetadata", function() {
+  metadata = true;
+  ok(v.seeking, "element is seeking once readyState is HAVE_METADATA");
+});
+once(v, "ended", function() {
+  ok(seekStarted, "seek should have started");
+  ok(seekCompleted, "seek should have completed");
+  ok(metadata, "loadedmetadata fired");
+  ok(v.currentTime >= SEEK_TIME, "currentTime should be after seek time");
+  SimpleTest.finish();
+});
+
+v.src = "seek.webm";
+v.play();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -172,35 +172,35 @@ AudioNode::DisconnectFromGraph()
     // It doesn't matter which one we remove, since we're going to remove all
     // entries for this node anyway.
     output->RemoveInputNode(inputIndex);
   }
 
   DestroyMediaStream();
 }
 
-void
+AudioNode*
 AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
                    uint32_t aInput, ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs() ||
       aInput >= aDestination.NumberOfInputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
-    return;
+    return nullptr;
   }
 
   if (Context() != aDestination.Context()) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-    return;
+    return nullptr;
   }
 
   if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, aOutput) !=
       nsTArray<AudioNode::InputNode>::NoIndex) {
     // connection already exists.
-    return;
+    return &aDestination;
   }
 
   // The MediaStreamGraph will handle cycle detection. We don't need to do it
   // here.
 
   mOutputNodes.AppendElement(&aDestination);
   InputNode* input = aDestination.mInputNodes.AppendElement();
   input->mInputNode = this;
@@ -215,16 +215,18 @@ AudioNode::Connect(AudioNode& aDestinati
       AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK,
                         static_cast<uint16_t>(aInput),
                         static_cast<uint16_t>(aOutput));
   }
   aDestination.NotifyInputsChanged();
 
   // This connection may have connected a panner and a source.
   Context()->UpdatePannerSource();
+
+  return &aDestination;
 }
 
 void
 AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput,
                    ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
--- a/dom/media/webaudio/AudioNode.h
+++ b/dom/media/webaudio/AudioNode.h
@@ -84,18 +84,18 @@ public:
     return mContext;
   }
 
   AudioContext* Context() const
   {
     return mContext;
   }
 
-  virtual void Connect(AudioNode& aDestination, uint32_t aOutput,
-                       uint32_t aInput, ErrorResult& aRv);
+  virtual AudioNode* Connect(AudioNode& aDestination, uint32_t aOutput,
+                             uint32_t aInput, ErrorResult& aRv);
 
   virtual void Connect(AudioParam& aDestination, uint32_t aOutput,
                        ErrorResult& aRv);
 
   virtual void Disconnect(uint32_t aOutput, ErrorResult& aRv);
 
   // Called after input nodes have been explicitly added or removed through
   // the Connect() or Disconnect() methods.
--- a/dom/media/webaudio/ScriptProcessorNode.h
+++ b/dom/media/webaudio/ScriptProcessorNode.h
@@ -28,23 +28,24 @@ public:
 
   IMPL_EVENT_HANDLER(audioprocess)
 
   virtual void EventListenerAdded(nsIAtom* aType) override;
   virtual void EventListenerRemoved(nsIAtom* aType) override;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  virtual void Connect(AudioNode& aDestination, uint32_t aOutput,
-                       uint32_t aInput, ErrorResult& aRv) override
+  virtual AudioNode* Connect(AudioNode& aDestination, uint32_t aOutput,
+                             uint32_t aInput, ErrorResult& aRv) override
   {
-    AudioNode::Connect(aDestination, aOutput, aInput, aRv);
+    AudioNode* node = AudioNode::Connect(aDestination, aOutput, aInput, aRv);
     if (!aRv.Failed()) {
       UpdateConnectedStatus();
     }
+    return node;
   }
 
   virtual void Connect(AudioParam& aDestination, uint32_t aOutput,
                        ErrorResult& aRv) override
   {
     AudioNode::Connect(aDestination, aOutput, aRv);
     if (!aRv.Failed()) {
       UpdateConnectedStatus();
--- a/dom/webidl/AudioNode.webidl
+++ b/dom/webidl/AudioNode.webidl
@@ -19,17 +19,17 @@ enum ChannelCountMode {
 enum ChannelInterpretation {
     "speakers",
     "discrete"
 };
 
 interface AudioNode : EventTarget {
 
     [Throws]
-    void connect(AudioNode destination, optional unsigned long output = 0, optional unsigned long input = 0);
+    AudioNode connect(AudioNode destination, optional unsigned long output = 0, optional unsigned long input = 0);
     [Throws]
     void connect(AudioParam destination, optional unsigned long output = 0);
     [Throws]
     void disconnect(optional unsigned long output = 0);
 
     readonly attribute AudioContext context;
     readonly attribute unsigned long numberOfInputs;
     readonly attribute unsigned long numberOfOutputs;
new file mode 100644
--- /dev/null
+++ b/dom/webidl/ChromeNodeList.webidl
@@ -0,0 +1,13 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[ChromeOnly, Constructor]
+interface ChromeNodeList : NodeList {
+  [Throws]
+  void append(Node aNode);
+  [Throws]
+  void remove(Node aNode);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -70,16 +70,17 @@ WEBIDL_FILES = [
     'CanvasCaptureMediaStream.webidl',
     'CanvasRenderingContext2D.webidl',
     'CaretPosition.webidl',
     'CDATASection.webidl',
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
     'CharacterData.webidl',
     'ChildNode.webidl',
+    'ChromeNodeList.webidl',
     'ChromeNotifications.webidl',
     'ChromeUtils.webidl',
     'Client.webidl',
     'Clients.webidl',
     'ClipboardEvent.webidl',
     'CommandEvent.webidl',
     'Comment.webidl',
     'CompositionEvent.webidl',
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -54,19 +54,19 @@ CancelChannelRunnable::Run()
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsresult rv = mChannel->Cancel(mStatus);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 FetchEvent::FetchEvent(EventTarget* aOwner)
-: ExtendableEvent(aOwner)
-, mIsReload(false)
-, mWaitToRespond(false)
+  : ExtendableEvent(aOwner)
+  , mIsReload(false)
+  , mWaitToRespond(false)
 {
 }
 
 FetchEvent::~FetchEvent()
 {
 }
 
 void
@@ -431,42 +431,34 @@ RespondWithHandler::CancelRequest(nsresu
 void
 FetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
 {
   if (EventPhase() == nsIDOMEvent::NONE || mWaitToRespond) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  // 4.5.3.2 If the respond-with entered flag is set, then:
-  // Throw an "InvalidStateError" exception.
-  // Here we use |mPromise != nullptr| as respond-with enter flag
-  if (mPromise) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  mPromise = &aArg;
-
   RefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
   StopImmediatePropagation();
   mWaitToRespond = true;
   RefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mRequest->Mode(), ir->IsClientRequest(),
                            ir->IsNavigationRequest(), mScriptSpec);
   aArg.AppendNativeHandler(handler);
+
+  WaitUntil(aArg, aRv);
 }
 
 NS_IMPL_ADDREF_INHERITED(FetchEvent, ExtendableEvent)
 NS_IMPL_RELEASE_INHERITED(FetchEvent, ExtendableEvent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchEvent)
 NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, ExtendableEvent,
-                                   mRequest, mPromise)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, ExtendableEvent, mRequest)
 
 ExtendableEvent::ExtendableEvent(EventTarget* aOwner)
   : Event(aOwner, nullptr, nullptr)
 {
 }
 
 void
 ExtendableEvent::WaitUntil(Promise& aPromise, ErrorResult& aRv)
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -98,17 +98,16 @@ public:
   }
 };
 
 class FetchEvent final : public ExtendableEvent
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   RefPtr<Request> mRequest;
   nsCString mScriptSpec;
-  RefPtr<Promise> mPromise;
   bool mIsReload;
   bool mWaitToRespond;
 protected:
   explicit FetchEvent(EventTarget* aOwner);
   ~FetchEvent();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
@@ -146,23 +145,16 @@ public:
   {
     return mIsReload;
   }
 
   void
   RespondWith(Promise& aArg, ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  GetPromise() const
-  {
-    RefPtr<Promise> p = mPromise;
-    return p.forget();
-  }
-
-  already_AddRefed<Promise>
   ForwardTo(const nsAString& aUrl);
 
   already_AddRefed<Promise>
   Default();
 };
 
 #ifndef MOZ_SIMPLEPUSH
 
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1170,21 +1170,21 @@ private:
         runnable = new CancelChannelRunnable(mInterceptedChannel, NS_ERROR_INTERCEPTION_CANCELED);
       } else {
         runnable = new ResumeRequest(mInterceptedChannel);
       }
 
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
     }
 
-    RefPtr<Promise> respondWithPromise = event->GetPromise();
-    if (respondWithPromise) {
+    RefPtr<Promise> waitUntilPromise = event->GetPromise();
+    if (waitUntilPromise) {
       RefPtr<KeepAliveHandler> keepAliveHandler =
         new KeepAliveHandler(mKeepAliveToken);
-      respondWithPromise->AppendNativeHandler(keepAliveHandler);
+      waitUntilPromise->AppendNativeHandler(keepAliveHandler);
     }
 
     // 9.8.22 If request is a non-subresource request, then: Invoke Soft Update algorithm
     if (internalReq->IsNavigationRequest()) {
       nsCOMPtr<nsIRunnable> runnable= new SoftUpdateRequest(mRegistration);
 
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
     }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae6a8a6b88403959c75efce931b0bf4293efc956
GIT binary patch
literal 87
zc%17D@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=oTrOph(&MmkMjpU%%3$Q@ydZf
kW_Mm0(}F7pCT1xxd^;`67yW*X5Ktw9r>mdKI;Vst0D!m{_W%F@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fe391dc8a2d797360651fe8cf77161a3fc891194
GIT binary patch
literal 123
zc%17D@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEPM$7~ArY-_&utWBP~c%$*z@jL
z8rOSWjTbX5i}z1sXJLKaupmKJKx7SbQ&Xu!zy>}Ju4{~r2dxw|BEXUllDQ?<M0xZq
S5i$mv#^CAd=d#Wzp$PyI1tgjP
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script>
+var width, url, width2, url2;
+function maybeReport() {
+  if (width !== undefined && url !== undefined &&
+      width2 !== undefined && url2 !== undefined) {
+    window.parent.postMessage({status: "result",
+                               width: width,
+                               width2: width2,
+                               url: url,
+                               url2: url2}, "*");
+  }
+}
+onload = function() {
+  width = document.querySelector("img").width;
+  width2 = document.querySelector("img").width;
+  maybeReport();
+};
+navigator.serviceWorker.onmessage = function(event) {
+  if (event.data.suffix == "2") {
+    url2 = event.data.url;
+  } else {
+    url = event.data.url;
+  }
+  maybeReport();
+};
+</script>
+<img src="image.png">
+<img src="image2.png">
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/maxage_test.js
@@ -0,0 +1,41 @@
+function synthesizeImage(suffix) {
+  // Serve image-20px for the first page, and image-40px for the second page.
+  return clients.matchAll().then(clients => {
+    var url = "image-20px.png";
+    clients.forEach(client => {
+      if (client.url.indexOf("?new") > 0) {
+        url = "image-40px.png";
+      }
+      client.postMessage({suffix: suffix, url: url});
+    });
+    return fetch(url);
+  }).then(response => {
+    return response.arrayBuffer();
+  }).then(ab => {
+    var headers;
+    if (suffix == "") {
+      headers = {
+        "Content-Type": "image/png",
+        "Date": "Tue, 1 Jan 1990 01:02:03 GMT",
+        "Cache-Control": "max-age=1",
+      };
+    } else {
+      headers = {
+        "Content-Type": "image/png",
+        "Cache-Control": "no-cache",
+      };
+    }
+    return new Response(ab, {
+      status: 200,
+      headers: headers,
+    });
+  });
+}
+
+self.addEventListener("fetch", function(event) {
+  if (event.request.url.indexOf("image.png") >= 0) {
+    event.respondWith(synthesizeImage(""));
+  } else if (event.request.url.indexOf("image2.png") >= 0) {
+    event.respondWith(synthesizeImage("2"));
+  }
+});
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+  function ok(v, msg) {
+    window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+  }
+
+  function done(reg) {
+    ok(reg.active, "The active worker should be available.");
+    window.parent.postMessage({status: "registrationdone"}, "*");
+  }
+
+  navigator.serviceWorker.ready.then(done);
+  navigator.serviceWorker.register("maxage_test.js", {scope: "."});
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+  navigator.serviceWorker.getRegistration(".").then(function(registration) {
+    registration.unregister().then(function(success) {
+      if (success) {
+        window.parent.postMessage({status: "unregistrationdone"}, "*");
+      }
+    }, function(e) {
+      dump("Unregistering the SW failed with " + e + "\n");
+    });
+  });
+</script>
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -54,16 +54,22 @@ support-files =
   fetch/https/index.html
   fetch/https/register.html
   fetch/https/unregister.html
   fetch/https/https_test.js
   fetch/https/clonedresponse/index.html
   fetch/https/clonedresponse/register.html
   fetch/https/clonedresponse/unregister.html
   fetch/https/clonedresponse/https_test.js
+  fetch/imagecache-maxage/index.html
+  fetch/imagecache-maxage/image-20px.png
+  fetch/imagecache-maxage/image-40px.png
+  fetch/imagecache-maxage/maxage_test.js
+  fetch/imagecache-maxage/register.html
+  fetch/imagecache-maxage/unregister.html
   fetch/interrupt.sjs
   fetch/origin/index.sjs
   fetch/origin/index-to-https.sjs
   fetch/origin/realindex.html
   fetch/origin/realindex.html^headers^
   fetch/origin/register.html
   fetch/origin/unregister.html
   fetch/origin/origin_test.js
@@ -277,8 +283,9 @@ skip-if = toolkit == "android" || toolki
 [test_unresolved_fetch_interception.html]
 [test_hsts_upgrade_intercept.html]
 skip-if = e10s # Bug 1214305
 [test_csp_upgrade-insecure_intercept.html]
 skip-if = e10s # Bug 1214305
 [test_serviceworker_header.html]
 [test_openWindow.html]
 skip-if = toolkit == "android" || toolkit == "gonk" || e10s
+[test_imagecache_max_age.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_imagecache_max_age.html
@@ -0,0 +1,75 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that the image cache respects a synthesized image's Cache headers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  var iframe;
+  var framesLoaded = 0;
+  function runTest() {
+    iframe = document.querySelector("iframe");
+    iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/register.html";
+    window.onmessage = function(e) {
+      if (e.data.status == "ok") {
+        ok(e.data.result, e.data.message);
+      } else if (e.data.status == "registrationdone") {
+        iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html";
+      } else if (e.data.status == "result") {
+        switch (++framesLoaded) {
+        case 1:
+          is(e.data.url, "image-20px.png", "Correct url expected");
+          is(e.data.url2, "image-20px.png", "Correct url expected");
+          is(e.data.width, 20, "Correct width expected");
+          is(e.data.width2, 20, "Correct width expected");
+          // Wait for 100ms so that the image gets expired.
+          setTimeout(function() {
+            iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/index.html?new"
+          }, 100);
+          break;
+        case 2:
+          is(e.data.url, "image-40px.png", "Correct url expected");
+          is(e.data.url2, "image-40px.png", "Correct url expected");
+          // TODO: Uncomment this check when bug 1217571 gets fixed.
+          // Currently because of bug 1217571, the QI in imgCacheValidator::OnStartRequest()
+          // to nsICachingChannel fails, which causes the check below to fail in non-e10s.
+          //is(e.data.width, 40, "Correct width expected");
+          //is(e.data.width2, 40, "Correct width expected");
+          iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/imagecache-maxage/unregister.html";
+          break;
+        default:
+          ok(false, "This should never happen");
+        }
+      } else if (e.data.status == "unregistrationdone") {
+        window.onmessage = null;
+        SimpleTest.finish();
+      }
+    };
+  }
+
+  SimpleTest.requestFlakyTimeout("This test needs to simulate the passing of time");
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.serviceWorkers.interception.enabled", true],
+    ]}, runTest);
+  };
+</script>
+</pre>
+</body>
+</html>
--- a/dom/xbl/test/test_bug389322.xhtml
+++ b/dom/xbl/test/test_bug389322.xhtml
@@ -111,16 +111,16 @@ function report(testName, success) {
     var success = true;
   }
   catch (e) { success = false; }
   report("HTML script tags with explicit version", success)
 ]]></script>
 <script type="text/javascript"><![CDATA[
   try {
     eval("let x = 1;");
-    var success = false;
+    var success = true;
   }
-  catch (e) { success = true; }
-  is(success, true, "JS 1.7 should not work in versionless HTML script tags");
+  catch (e) { success = false; }
+  is(success, true, "let should work in versionless HTML script tags");
 ]]></script>
 </pre>
 </body>
 </html>
--- a/editor/libeditor/nsHTMLDataTransfer.cpp
+++ b/editor/libeditor/nsHTMLDataTransfer.cpp
@@ -1016,73 +1016,62 @@ nsHTMLEditor::ParseCFHTML(nsCString & aC
 nsresult nsHTMLEditor::InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe,
                                     nsIDOMDocument *aSourceDoc,
                                     nsIDOMNode *aDestinationNode,
                                     int32_t aDestOffset,
                                     bool aDoDeleteSelection)
 {
   nsresult rv;
 
-  const char* type = aType;
+  nsAutoCString type(aType);
 
   // Check to see if we can insert an image file
   bool insertAsImage = false;
-  nsCOMPtr<nsIURI> fileURI;
-  if (0 == nsCRT::strcmp(type, kFileMime))
+  nsCOMPtr<nsIFile> fileObj;
+  if (type.EqualsLiteral(kFileMime))
   {
-    nsCOMPtr<nsIFile> fileObj = do_QueryInterface(aObject);
+    fileObj = do_QueryInterface(aObject);
     if (fileObj)
     {
-      rv = NS_NewFileURI(getter_AddRefs(fileURI), fileObj);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
-      NS_ENSURE_TRUE(mime, NS_ERROR_FAILURE);
-      nsAutoCString contentType;
-      rv = mime->GetTypeFromFile(fileObj, contentType);
-      NS_ENSURE_SUCCESS(rv, rv);
-
       // Accept any image type fed to us
-      if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
+      if (nsContentUtils::IsFileImage(fileObj, type))
+      {
         insertAsImage = true;
-        type = contentType.get();
+      }
+      else
+      {
+        // Reset type.
+        type.AssignLiteral(kFileMime);
       }
     }
   }
 
-  if (0 == nsCRT::strcmp(type, kJPEGImageMime) ||
-      0 == nsCRT::strcmp(type, kJPGImageMime) ||
-      0 == nsCRT::strcmp(type, kPNGImageMime) ||
-      0 == nsCRT::strcmp(type, kGIFImageMime) ||
+  if (type.EqualsLiteral(kJPEGImageMime) ||
+      type.EqualsLiteral(kJPGImageMime) ||
+      type.EqualsLiteral(kPNGImageMime) ||
+      type.EqualsLiteral(kGIFImageMime) ||
       insertAsImage)
   {
-    nsCOMPtr<nsIInputStream> imageStream;
-    if (insertAsImage) {
-      NS_ASSERTION(fileURI, "The file URI should be retrieved earlier");
-
-      nsCOMPtr<nsIChannel> channel;
-      rv = NS_NewChannel(getter_AddRefs(channel),
-                         fileURI,
-                         nsContentUtils::GetSystemPrincipal(),
-                         nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
-                         nsIContentPolicy::TYPE_OTHER);
+    nsCString imageData;
+    if (insertAsImage)
+    {
+      rv = nsContentUtils::SlurpFileToString(fileObj, imageData);
       NS_ENSURE_SUCCESS(rv, rv);
-      rv = channel->Open2(getter_AddRefs(imageStream));
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      imageStream = do_QueryInterface(aObject);
+    }
+    else
+    {
+      nsCOMPtr<nsIInputStream> imageStream = do_QueryInterface(aObject);
       NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE);
-    }
 
-    nsCString imageData;
-    rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
-    NS_ENSURE_SUCCESS(rv, rv);
+      rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
+      NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = imageStream->Close();
-    NS_ENSURE_SUCCESS(rv, rv);
+      rv = imageStream->Close();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     nsAutoCString data64;
     rv = Base64Encode(imageData, data64);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsAutoString stuffToPaste;
     stuffToPaste.AssignLiteral("<IMG src=\"data:");
     AppendUTF8toUTF16(type, stuffToPaste);
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1611,17 +1611,19 @@ public:
 
   static bool IsLogEnabled() { return LayerManager::IsLogEnabled(); }
 
   /**
    * Returns the current area of the layer (in layer-space coordinates)
    * marked as needed to be recomposited.
    */
   const nsIntRegion& GetInvalidRegion() { return mInvalidRegion; }
-  const void SetInvalidRegion(const nsIntRegion& aRect) { mInvalidRegion = aRect; }
+  const void AddInvalidRegion(const nsIntRegion& aRegion) {
+    mInvalidRegion.Or(mInvalidRegion, aRegion);
+  }
 
   /**
    * Mark the entirety of the layer's visible region as being invalid.
    */
   void SetInvalidRectToVisibleRegion() { mInvalidRegion = GetVisibleRegion(); }
 
   /**
    * Adds to the current invalid rect.
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -301,19 +301,22 @@ RotatedContentBuffer::BorrowDrawTargetFo
                                                  -quadrantRect.y));
 
   return mLoanedDrawTarget;
 }
 
 void
 BorrowDrawTarget::ReturnDrawTarget(gfx::DrawTarget*& aReturned)
 {
+  MOZ_ASSERT(mLoanedDrawTarget);
   MOZ_ASSERT(aReturned == mLoanedDrawTarget);
-  mLoanedDrawTarget->SetTransform(mLoanedTransform);
-  mLoanedDrawTarget = nullptr;
+  if (mLoanedDrawTarget) {
+    mLoanedDrawTarget->SetTransform(mLoanedTransform);
+    mLoanedDrawTarget = nullptr;
+  }
   aReturned = nullptr;
 }
 
 gfxContentType
 RotatedContentBuffer::BufferContentType()
 {
   if (mBufferProvider || mDTBuffer) {
     SurfaceFormat format;
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -143,16 +143,17 @@ void
 LayerManagerComposite::Destroy()
 {
   if (!mDestroyed) {
     mCompositor->GetWidget()->CleanupWindowEffects();
     if (mRoot) {
       RootLayer()->Destroy();
     }
     mRoot = nullptr;
+    mClonedLayerTreeProperties = nullptr;
     mDestroyed = true;
   }
 }
 
 void
 LayerManagerComposite::UpdateRenderBounds(const IntRect& aRect)
 {
   mRenderBounds = aRect;
@@ -170,18 +171,16 @@ LayerManagerComposite::BeginTransaction(
 {
   mInTransaction = true;
   
   if (!mCompositor->Ready()) {
     return;
   }
   
   mIsCompositorReady = true;
-
-  mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
 }
 
 void
 LayerManagerComposite::BeginTransactionWithDrawTarget(DrawTarget* aTarget, const IntRect& aRect)
 {
   mInTransaction = true;
   
   if (!mCompositor->Ready()) {
@@ -277,64 +276,97 @@ LayerManagerComposite::EndTransaction(co
     return;
   }
 
   // Set composition timestamp here because we need it in
   // ComputeEffectiveTransforms (so the correct video frame size is picked) and
   // also to compute invalid regions properly.
   mCompositor->SetCompositionTime(aTimeStamp);
 
-  if (mRoot && mClonedLayerTreeProperties) {
-    MOZ_ASSERT(!mTarget);
-    nsIntRegion invalid =
-      mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &mGeometryChanged);
-    mClonedLayerTreeProperties = nullptr;
-
-    mInvalidRegion.Or(mInvalidRegion, invalid);
-  } else if (!mTarget) {
-    mInvalidRegion.Or(mInvalidRegion, mRenderBounds);
-  }
-
-  if (mInvalidRegion.IsEmpty() && !mTarget) {
-    // Composition requested, but nothing has changed. Don't do any work.
-    return;
-  }
-
-  // We don't want our debug overlay to cause more frames to happen
-  // so we will invalidate after we've decided if something changed.
-  InvalidateDebugOverlay(mRenderBounds);
-
- if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) {
+  if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) {
     MOZ_ASSERT(!aTimeStamp.IsNull());
-    // The results of our drawing always go directly into a pixel buffer,
-    // so we don't need to pass any global transform here.
-    mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
-
-    nsIntRegion opaque;
-    ApplyOcclusionCulling(mRoot, opaque);
-
-    Render();
-#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
-    RenderToPresentationSurface();
-#endif
-    mGeometryChanged = false;
+    UpdateAndRender();
   } else {
-    // Modified layer tree
+    // Modified the layer tree.
     mGeometryChanged = true;
   }
 
   mCompositor->ClearTargetContext();
   mTarget = nullptr;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   Log();
   MOZ_LAYERS_LOG(("]----- EndTransaction"));
 #endif
 }
 
+void
+LayerManagerComposite::UpdateAndRender()
+{
+  nsIntRegion invalid;
+
+  if (mClonedLayerTreeProperties) {
+    // We need to compute layer tree differences even if we're not going to
+    // immediately use the resulting damage area, since ComputeDifferences
+    // is also responsible for invalidates intermediate surfaces in
+    // ContainerLayers.
+    nsIntRegion changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &mGeometryChanged);
+
+    if (mTarget) {
+      // Since we're composing to an external target, we're not going to use
+      // the damage region from layers changes - we want to composite
+      // everything in the target bounds. Instead we accumulate the layers
+      // damage region for the next window composite.
+      mInvalidRegion.Or(mInvalidRegion, changed);
+    } else {
+      invalid = Move(changed);
+    }
+  }
+
+  if (mTarget) {
+    invalid.Or(invalid, mTargetBounds);
+  } else {
+    // If we didn't have a previous layer tree, invalidate the entire render
+    // area.
+    if (!mClonedLayerTreeProperties) {
+      invalid.Or(invalid, mRenderBounds);
+    }
+
+    // Add any additional invalid rects from the window manager or previous
+    // damage computed during ComposeToTarget().
+    invalid.Or(invalid, mInvalidRegion);
+    mInvalidRegion.SetEmpty();
+  }
+
+  // Update cached layer tree information.
+  mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
+
+  if (invalid.IsEmpty()) {
+    // Composition requested, but nothing has changed. Don't do any work.
+    return;
+  }
+
+  // We don't want our debug overlay to cause more frames to happen
+  // so we will invalidate after we've decided if something changed.
+  InvalidateDebugOverlay(mRenderBounds);
+
+  // The results of our drawing always go directly into a pixel buffer,
+  // so we don't need to pass any global transform here.
+  mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
+
+  nsIntRegion opaque;
+  ApplyOcclusionCulling(mRoot, opaque);
+
+  Render(invalid);
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+  RenderToPresentationSurface();
+#endif
+  mGeometryChanged = false;
+}
+
 already_AddRefed<DrawTarget>
 LayerManagerComposite::CreateOptimalMaskDrawTarget(const IntSize &aSize)
 {
   NS_RUNTIMEABORT("Should only be called on the drawing side");
   return nullptr;
 }
 
 already_AddRefed<PaintedLayer>
@@ -636,17 +668,17 @@ LayerManagerComposite::PopGroupForLayerE
   effectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX] = new EffectColorMatrix(effectMatrix);
 
   gfx::Rect clipRectF(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
   mCompositor->DrawQuad(Rect(Point(0, 0), Size(mTwoPassTmpTarget->GetSize())), clipRectF, effectChain, 1.,
                         Matrix4x4());
 }
 
 void
-LayerManagerComposite::Render()
+LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion)
 {
   PROFILER_LABEL("LayerManagerComposite", "Render",
     js::ProfileEntry::Category::GRAPHICS);
 
   if (mDestroyed) {
     NS_WARNING("Call on destroyed layer manager");
     return;
   }
@@ -696,55 +728,44 @@ LayerManagerComposite::Render()
     LayerScope::SetHWComposed();
     if (mFPS) {
       double fps = mFPS->mCompositionFps.AddFrameAndGetFps(TimeStamp::Now());
       if (gfxPrefs::LayersDrawFPS()) {
         printf_stderr("HWComposer: FPS is %g\n", fps);
       }
     }
     mCompositor->EndFrameForExternalComposition(Matrix());
-    // Reset the invalid region as compositing is done
-    mInvalidRegion.SetEmpty();
     mLastFrameMissedHWC = false;
     return;
   } else if (!mTarget && !haveLayerEffects) {
     mLastFrameMissedHWC = !!composer2D;
   }
 
   {
     PROFILER_LABEL("LayerManagerComposite", "PreRender",
       js::ProfileEntry::Category::GRAPHICS);
 
     if (!mCompositor->GetWidget()->PreRender(this)) {
       return;
     }
   }
 
-  nsIntRegion invalid;
-  if (mTarget) {
-    invalid = mTargetBounds;
-  } else {
-    invalid = mInvalidRegion;
-    // Reset the invalid region now that we've begun compositing.
-    mInvalidRegion.SetEmpty();
-  }
-
   ParentLayerIntRect clipRect;
   Rect bounds(mRenderBounds.x, mRenderBounds.y, mRenderBounds.width, mRenderBounds.height);
   Rect actualBounds;
 
   CompositorBench(mCompositor, bounds);
 
   if (mRoot->GetClipRect()) {
     clipRect = *mRoot->GetClipRect();
     Rect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
-    mCompositor->BeginFrame(invalid, &rect, bounds, nullptr, &actualBounds);
+    mCompositor->BeginFrame(aInvalidRegion, &rect, bounds, nullptr, &actualBounds);
   } else {
     gfx::Rect rect;
-    mCompositor->BeginFrame(invalid, nullptr, bounds, &rect, &actualBounds);
+    mCompositor->BeginFrame(aInvalidRegion, nullptr, bounds, &rect, &actualBounds);
     clipRect = ParentLayerIntRect(rect.x, rect.y, rect.width, rect.height);
   }
 
   if (actualBounds.IsEmpty()) {
     mCompositor->GetWidget()->PostRender(this);
     return;
   }
 
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -293,19 +293,24 @@ private:
    * accumulated transform of intermediate surfaces beneath aLayer.
    */
   static void ComputeRenderIntegrityInternal(Layer* aLayer,
                                              nsIntRegion& aScreenRegion,
                                              nsIntRegion& aLowPrecisionScreenRegion,
                                              const gfx::Matrix4x4& aTransform);
 
   /**
+   * Update the invalid region and render it.
+   */
+  void UpdateAndRender();
+
+  /**
    * Render the current layer tree to the active target.
    */
-  void Render();
+  void Render(const nsIntRegion& aInvalidRegion);
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   void RenderToPresentationSurface();
 #endif
 
   /**
    * We need to know our invalid region before we're ready to render.
    */
   void InvalidateDebugOverlay(const gfx::IntRect& aBounds);
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -340,20 +340,23 @@ LayerTransactionParent::RecvUpdate(Infal
       layer->SetMixBlendMode((gfx::CompositionOp)common.mixBlendMode());
       layer->SetForceIsolatedGroup(common.forceIsolatedGroup());
       if (PLayerParent* maskLayer = common.maskLayerParent()) {
         layer->SetMaskLayer(cast(maskLayer)->AsLayer());
       } else {
         layer->SetMaskLayer(nullptr);
       }
       layer->SetAnimations(common.animations());
-      layer->SetInvalidRegion(common.invalidRegion());
       layer->SetFrameMetrics(common.metrics());
       layer->SetDisplayListLog(common.displayListLog().get());
 
+      // The updated invalid region is added to the existing one, since we can
+      // update multiple times before the next composite.
+      layer->AddInvalidRegion(common.invalidRegion());
+
       nsTArray<RefPtr<Layer>> maskLayers;
       for (size_t i = 0; i < common.ancestorMaskLayersParent().Length(); i++) {
         Layer* maskLayer = cast(common.ancestorMaskLayersParent().ElementAt(i))->AsLayer();
         maskLayers.AppendElement(maskLayer);
       }
       layer->SetAncestorMaskLayers(maskLayers);
 
       typedef SpecificLayerAttributes Specific;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -289,16 +289,17 @@ private:
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
   DECL_GFX_PREF(Live, "image.single-color-optimization.enabled", ImageSingleColorOptimizationEnabled, bool, true);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabled, bool, false);
+  DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled",     LayersAMDSwitchableGfxEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled",         AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.separate-event-thread", AsyncPanZoomSeparateEventThread, bool, false);
   DECL_GFX_PREF(Live, "layers.bench.enabled",                  LayersBenchEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.bufferrotation.enabled",         BufferRotationEnabled, bool, true);
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
   // and ignore the preference.
   DECL_GFX_PREF(Skip, "layers.componentalpha.enabled",         ComponentAlphaEnabled, bool, false);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1853,36 +1853,49 @@ bool DoesD3D11TextureSharingWorkInternal
 
   if (GetModuleHandleW(L"atidxx32.dll")) {
     nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
     if (gfxInfo) {
       nsString vendorID, vendorID2;
       gfxInfo->GetAdapterVendorID(vendorID);
       gfxInfo->GetAdapterVendorID2(vendorID2);
       if (vendorID.EqualsLiteral("0x8086") && vendorID2.IsEmpty()) {
-        gfxCriticalError(CriticalLog::DefaultOptions(false)) << "Unexpected Intel/AMD dual-GPU setup";
-        return false;
+        if (!gfxPrefs::LayersAMDSwitchableGfxEnabled()) {
+          return false;
+        }
+        gfxCriticalError(CriticalLog::DefaultOptions(false)) << "PossiblyBrokenSurfaceSharing_UnexpectedAMDGPU";
       }
     }
   }
 
   RefPtr<ID3D11Texture2D> texture;
   D3D11_TEXTURE2D_DESC desc;
-  desc.Width = 32;
-  desc.Height = 32;
+  const int texture_size = 32;
+  desc.Width = texture_size;
+  desc.Height = texture_size;
   desc.MipLevels = 1;
   desc.ArraySize = 1;
   desc.Format = format;
   desc.SampleDesc.Count = 1;
   desc.SampleDesc.Quality = 0;
   desc.Usage = D3D11_USAGE_DEFAULT;
   desc.CPUAccessFlags = 0;
   desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
   desc.BindFlags = bindflags;
-  if (FAILED(device->CreateTexture2D(&desc, NULL, getter_AddRefs(texture)))) {
+
+  uint32_t color[texture_size * texture_size];
+  for (size_t i = 0; i < sizeof(color)/sizeof(color[0]); i++) {
+    color[i] = 0xff00ffff;
+  }
+  // We're going to check that sharing actually works with this format
+  D3D11_SUBRESOURCE_DATA data;
+  data.pSysMem = color;
+  data.SysMemPitch = texture_size * 4;
+  data.SysMemSlicePitch = 0;
+  if (FAILED(device->CreateTexture2D(&desc, &data, getter_AddRefs(texture)))) {
     return false;
   }
 
   HANDLE shareHandle;
   RefPtr<IDXGIResource> otherResource;
   if (FAILED(texture->QueryInterface(__uuidof(IDXGIResource),
                                      getter_AddRefs(otherResource))))
   {
@@ -1903,16 +1916,60 @@ bool DoesD3D11TextureSharingWorkInternal
   }
 
   if (FAILED(sharedResource->QueryInterface(__uuidof(ID3D11Texture2D),
                                             getter_AddRefs(sharedTexture))))
   {
     return false;
   }
 
+  // create a staging texture for readback
+  RefPtr<ID3D11Texture2D> cpuTexture;
+  desc.Usage = D3D11_USAGE_STAGING;
+  desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+  desc.MiscFlags = 0;
+  desc.BindFlags = 0;
+  if (FAILED(device->CreateTexture2D(&desc, nullptr, getter_AddRefs(cpuTexture)))) {
+    return false;
+  }
+
+  RefPtr<IDXGIKeyedMutex> sharedMutex;
+  RefPtr<ID3D11DeviceContext> deviceContext;
+  sharedResource->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)getter_AddRefs(sharedMutex));
+  device->GetImmediateContext(getter_AddRefs(deviceContext));
+  if (FAILED(sharedMutex->AcquireSync(0, 30*1000))) {
+    gfxCriticalError() << "DoesD3D11TextureSharingWork_AcquireSyncTimeout";
+    // only wait for 30 seconds
+    return false;
+  }
+
+  // Copy to the cpu texture so that we can readback
+  deviceContext->CopyResource(cpuTexture, sharedTexture);
+
+  D3D11_MAPPED_SUBRESOURCE mapped;
+  int resultColor = 0;
+  if (SUCCEEDED(deviceContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped))) {
+    // read the texture
+    resultColor = *(int*)mapped.pData;
+    deviceContext->Unmap(cpuTexture, 0);
+  } else {
+    gfxCriticalError() << "DoesD3D11TextureSharingWork_MapFailed";
+    return false;
+  }
+
+  sharedMutex->ReleaseSync(0);
+
+  // check that the color we put in is the color we get out
+  if (resultColor != color[0]) {
+    // Shared surfaces seem to be broken on dual AMD & Intel HW when using the
+    // AMD GPU
+    gfxCriticalNote << "DoesD3D11TextureSharingWork_ColorMismatch";
+    return false;
+  }
+
   RefPtr<ID3D11ShaderResourceView> sharedView;
 
   // This if(FAILED()) is the one that actually fails on systems affected by bug 1083071.
   if (FAILED(device->CreateShaderResourceView(sharedTexture, NULL, getter_AddRefs(sharedView)))) {
     gfxCriticalNote << "CreateShaderResourceView failed for format" << format;
     return false;
   }
 
rename from image/BMPFileHeaders.h
rename to image/BMPHeaders.h
--- a/image/BMPFileHeaders.h
+++ b/image/BMPHeaders.h
@@ -1,84 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef mozilla_image_BMPFileHeaders_h
-#define mozilla_image_BMPFileHeaders_h
+#ifndef mozilla_image_BMPHeaders_h
+#define mozilla_image_BMPHeaders_h
 
 #include <stddef.h>
 #include <stdint.h>
 
 namespace mozilla {
 namespace image {
 namespace bmp {
 
-// This length is stored in the |bihsize| field of bmp::FileHeader.
+// The length of the file header as defined in the BMP spec.
+static const size_t FILE_HEADER_LENGTH = 14;
+
+// This lengths of the info header for the different BMP versions.
 struct InfoHeaderLength {
   enum {
     WIN_V2 = 12,
     WIN_V3 = 40,
     WIN_V4 = 108,
     WIN_V5 = 124,
 
     // OS2_V1 is omitted; it's the same as WIN_V2.
     OS2_V2_MIN = 16,    // Minimum allowed value for OS2v2.
     OS2_V2_MAX = 64,    // Maximum allowed value for OS2v2.
+
+    WIN_ICO = WIN_V3,
   };
 };
 
-struct FileHeader {
-  char signature[2];   // String "BM".
-  uint32_t filesize;   // File size; unreliable in practice.
-  int32_t reserved;    // Zero.
-  uint32_t dataoffset; // Offset to raster data.
-
-  // The length of the file header as defined in the BMP spec.
-  static const size_t LENGTH = 14;
-};
-
-struct XYZ {
-  int32_t x, y, z;
-};
-
-struct XYZTriple {
-  XYZ r, g, b;
-};
-
-struct V5InfoHeader {
-  uint32_t bihsize;          // Header size
-  int32_t width;             // Uint16 in OS/2 BMPs
-  int32_t height;            // Uint16 in OS/2 BMPs
-  uint16_t planes;           // =1
-  uint16_t bpp;              // Bits per pixel.
-  // The rest of the header is not available in WIN_V2/OS2_V1 BMP Files
-  uint32_t compression;      // See Compression for valid values
-  uint32_t image_size;       // (compressed) image size. Can be 0 if
-                             // compression==0
-  uint32_t xppm;             // Pixels per meter, horizontal
-  uint32_t yppm;             // Pixels per meter, vertical
-  uint32_t colors;           // Used Colors
-  uint32_t important_colors; // Number of important colors. 0=all
-  uint32_t red_mask;         // Bits used for red component
-  uint32_t green_mask;       // Bits used for green component
-  uint32_t blue_mask;        // Bits used for blue component
-  uint32_t alpha_mask;       // Bits used for alpha component
-  uint32_t color_space;      // 0x73524742=LCS_sRGB ...
-  // These members are unused unless color_space == LCS_CALIBRATED_RGB
-  XYZTriple white_point;     // Logical white point
-  uint32_t gamma_red;        // Red gamma component
-  uint32_t gamma_green;      // Green gamma component
-  uint32_t gamma_blue;       // Blue gamma component
-  uint32_t intent;           // Rendering intent
-  // These members are unused unless color_space == LCS_PROFILE_*
-  uint32_t profile_offset;   // Offset to profile data in bytes
-  uint32_t profile_size;     // Size of profile data in bytes
-  uint32_t reserved;         // =0
-
-  static const uint32_t COLOR_SPACE_LCS_SRGB = 0x73524742;
-};
-
 } // namespace bmp
 } // namespace image
 } // namespace mozilla
 
-#endif // mozilla_image_BMPFileHeaders_h
+#endif // mozilla_image_BMPHeaders_h
--- a/image/Downscaler.cpp
+++ b/image/Downscaler.cpp
@@ -101,35 +101,35 @@ Downscaler::BeginFrame(const nsIntSize& 
 
   skia::resize::ComputeFilters(resizeMethod,
                                mOriginalSize.height, mTargetSize.height,
                                0, mTargetSize.height,
                                mYFilter.get());
 
   // Allocate the buffer, which contains scanlines of the original image.
   // pad by 15 to handle overreads by the simd code
-  mRowBuffer = MakeUnique<uint8_t[]>(mOriginalSize.width * sizeof(uint32_t) + 15);
+  mRowBuffer.reset(new (fallible) uint8_t[mOriginalSize.width * sizeof(uint32_t) + 15]);
   if (MOZ_UNLIKELY(!mRowBuffer)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Allocate the window, which contains horizontally downscaled scanlines. (We
   // can store scanlines which are already downscale because our downscaling
   // filter is separable.)
   mWindowCapacity = mYFilter->max_filter();
-  mWindow = MakeUnique<uint8_t*[]>(mWindowCapacity);
+  mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
   if (MOZ_UNLIKELY(!mWindow)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   bool anyAllocationFailed = false;
   // pad by 15 to handle overreads by the simd code
   const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15;
   for (int32_t i = 0; i < mWindowCapacity; ++i) {
-    mWindow[i] = new uint8_t[rowSize];
+    mWindow[i] = new (fallible) uint8_t[rowSize];
     anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
   }
 
   if (MOZ_UNLIKELY(anyAllocationFailed)) {
     // We intentionally iterate through the entire array even if an allocation
     // fails, to ensure that all the pointers in it are either valid or nullptr.
     // That in turn ensures that ReleaseWindow() can clean up correctly.
     return NS_ERROR_OUT_OF_MEMORY;
--- a/image/SourceBuffer.h
+++ b/image/SourceBuffer.h
@@ -268,63 +268,61 @@ private:
   class Chunk
   {
   public:
     explicit Chunk(size_t aCapacity)
       : mCapacity(aCapacity)
       , mLength(0)
     {
       MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
-      mData = new (fallible) char[mCapacity];
+      mData.reset(new (fallible) char[mCapacity]);
     }
 
-    ~Chunk() { delete[] mData; }
-
     Chunk(Chunk&& aOther)
       : mCapacity(aOther.mCapacity)
       , mLength(aOther.mLength)
-      , mData(aOther.mData)
+      , mData(Move(aOther.mData))
     {
       aOther.mCapacity = aOther.mLength = 0;
       aOther.mData = nullptr;
     }
 
     Chunk& operator=(Chunk&& aOther)
     {
       mCapacity = aOther.mCapacity;
       mLength = aOther.mLength;
-      mData = aOther.mData;
+      mData = Move(aOther.mData);
       aOther.mCapacity = aOther.mLength = 0;
       aOther.mData = nullptr;
       return *this;
     }
 
     bool AllocationFailed() const { return !mData; }
     size_t Capacity() const { return mCapacity; }
     size_t Length() const { return mLength; }
 
     char* Data() const
     {
       MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
-      return mData;
+      return mData.get();
     }
 
     void AddLength(size_t aAdditionalLength)
     {
       MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity);
       mLength += aAdditionalLength;
     }
 
   private:
     Chunk(const Chunk&) = delete;
     Chunk& operator=(const Chunk&) = delete;
 
     size_t mCapacity;
     size_t mLength;
-    char* mData;
+    UniquePtr<char[]> mData;
   };
 
   nsresult AppendChunk(Maybe<Chunk>&& aChunk);
   Maybe<Chunk> CreateChunk(size_t aCapacity, bool aRoundUp = true);
   nsresult Compact();
   static size_t RoundedUpCapacity(size_t aCapacity);
   size_t FibonacciCapacityWithMinimum(size_t aMinCapacity);
 
--- a/image/decoders/icon/win/nsIconChannel.cpp
+++ b/image/decoders/icon/win/nsIconChannel.cpp
@@ -570,21 +570,21 @@ nsIconChannel::MakeInputStream(nsIInputS
           (colorTableSize = GetColorTableSize(&colorHeader)) >= 0 &&
           (maskTableSize  = GetColorTableSize(&maskHeader))  >= 0) {
         uint32_t iconSize = sizeof(ICONFILEHEADER) +
                             sizeof(ICONENTRY) +
                             sizeof(BITMAPINFOHEADER) +
                             colorHeader.biSizeImage +
                             maskHeader.biSizeImage;
 
-        char* buffer = new char[iconSize];
+        UniquePtr<char[]> buffer = MakeUnique<char[]>(iconSize);
         if (!buffer) {
           rv = NS_ERROR_OUT_OF_MEMORY;
         } else {
-          char* whereTo = buffer;
+          char* whereTo = buffer.get();
           int howMuch;
 
           // the data starts with an icon file header
           ICONFILEHEADER iconHeader;
           iconHeader.ifhReserved = 0;
           iconHeader.ifhType = 1;
           iconHeader.ifhCount = 1;
           howMuch = sizeof(ICONFILEHEADER);
@@ -635,27 +635,26 @@ nsIconChannel::MakeInputStream(nsIInputS
               // Now, create a pipe and stuff our data into it
               nsCOMPtr<nsIInputStream> inStream;
               nsCOMPtr<nsIOutputStream> outStream;
               rv = NS_NewPipe(getter_AddRefs(inStream),
                               getter_AddRefs(outStream),
                               iconSize, iconSize, aNonBlocking);
               if (NS_SUCCEEDED(rv)) {
                 uint32_t written;
-                rv = outStream->Write(buffer, iconSize, &written);
+                rv = outStream->Write(buffer.get(), iconSize, &written);
                 if (NS_SUCCEEDED(rv)) {
                   NS_ADDREF(*_retval = inStream);
                 }
               }
 
             } // if we got bitmap bits
             delete maskInfo;
           } // if we got mask bits
           delete colorInfo;
-          delete [] buffer;
         } // if we allocated the buffer
       } // if we got mask size
 
       DeleteDC(hDC);
       DeleteObject(iconInfo.hbmColor);
       DeleteObject(iconInfo.hbmMask);
     } // if we got icon info
     DestroyIcon(hIcon);
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -20,38 +20,38 @@
 //
 // WINDOWS VERSIONS OF THE BMP FORMAT
 // ----------------------------------
 // WinBMPv1.
 // - This version is no longer used and can be ignored.
 //
 // WinBMPv2.
 // - First is a 14 byte file header that includes: the magic number ("BM"),
-//   file size, and offset to the pixel data (|dataoffset|).
+//   file size, and offset to the pixel data (|mDataOffset|).
 // - Next is a 12 byte info header which includes: the info header size
-//   (bihsize), width, height, number of color planes, and bits-per-pixel
-//   (|bpp|) which must be 1, 4, 8 or 24.
-// - Next is the semi-optional color table, which has length 2^|bpp| and has 3
-//   bytes per value (BGR). The color table is required if |bpp| is 1, 4, or 8.
+//   (mBIHSize), width, height, number of color planes, and bits-per-pixel
+//   (|mBpp|) which must be 1, 4, 8 or 24.
+// - Next is the semi-optional color table, which has length 2^|mBpp| and has 3
+//   bytes per value (BGR). The color table is required if |mBpp| is 1, 4, or 8.
 // - Next is an optional gap.
-// - Next is the pixel data, which is pointed to by |dataoffset|.
+// - Next is the pixel data, which is pointed to by |mDataOffset|.
 //
 // WinBMPv3. This is the most widely used version.
 // - It changed the info header to 40 bytes by taking the WinBMPv2 info
 //   header, enlargening its width and height fields, and adding more fields
-//   including: a compression type (|compression|) and number of colors
-//   (|colors|).
+//   including: a compression type (|mCompression|) and number of colors
+//   (|mNumColors|).
 // - The semi-optional color table is now 4 bytes per value (BGR0), and its
-//   length is |colors|, or 2^|bpp| if |colors| is zero.
-// - |compression| can be RGB (i.e. no compression), RLE4 (if bpp==4) or RLE8
-//   (if bpp==8) values.
+//   length is |mNumColors|, or 2^|mBpp| if |mNumColors| is zero.
+// - |mCompression| can be RGB (i.e. no compression), RLE4 (if |mBpp|==4) or
+//   RLE8 (if |mBpp|==8) values.
 //
 // WinBMPv3-NT. A variant of WinBMPv3.
 // - It did not change the info header layout from WinBMPv3.
-// - |bpp| can now be 16 or 32, in which case |compression| can be RGB or the
+// - |mBpp| can now be 16 or 32, in which case |mCompression| can be RGB or the
 //   new BITFIELDS value; in the latter case an additional 12 bytes of color
 //   bitfields follow the info header.
 //
 // WinBMPv4.
 // - It extended the info header to 108 bytes, including the 12 bytes of color
 //   mask data from WinBMPv3-NT, plus alpha mask data, and also color-space and
 //   gamma correction fields.
 //
@@ -132,30 +132,31 @@ using namespace bmp;
 static void
 SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen,
          uint8_t aBlue, uint8_t aAlpha = 0xFF)
 {
   *aDecoded++ = gfxPackedPixel(aAlpha, aRed, aGreen, aBlue);
 }
 
 static void
-SetPixel(uint32_t*& aDecoded, uint8_t idx, bmp::ColorTableEntry* aColors)
+SetPixel(uint32_t*& aDecoded, uint8_t idx,
+         const UniquePtr<ColorTableEntry[]>& aColors)
 {
   SetPixel(aDecoded,
            aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue);
 }
 
 /// Sets two (or one if aCount = 1) pixels
 /// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes
 /// depending on whether one or two pixels are written.
 /// @param aData The values for the two pixels
 /// @param aCount Current count. Is decremented by one or two.
 static void
 Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount,
-             bmp::ColorTableEntry* aColors)
+             const UniquePtr<ColorTableEntry[]>& aColors)
 {
   uint8_t idx = aData >> 4;
   SetPixel(aDecoded, idx, aColors);
   if (--aCount > 0) {
     idx = aData & 0xF;
     SetPixel(aDecoded, idx, aColors);
     --aCount;
   }
@@ -166,93 +167,86 @@ GetBMPLog()
 {
   static PRLogModuleInfo* sBMPLog;
   if (!sBMPLog) {
     sBMPLog = PR_NewLogModule("BMPDecoder");
   }
   return sBMPLog;
 }
 
-nsBMPDecoder::nsBMPDecoder(RasterImage* aImage)
+// The length of the mBIHSize field in the info header.
+static const uint32_t BIHSIZE_FIELD_LENGTH = 4;
+
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength)
   : Decoder(aImage)
-  , mLexer(Transition::To(State::FILE_HEADER, FileHeader::LENGTH))
+  , mLexer(Transition::To(aState, aLength))
   , mIsWithinICO(false)
   , mMayHaveTransparency(false)
   , mDoesHaveTransparency(false)
   , mNumColors(0)
   , mColors(nullptr)
   , mBytesPerColor(0)
   , mPreGapLength(0)
   , mCurrentRow(0)
   , mCurrentPos(0)
   , mAbsoluteModeNumPixels(0)
 {
-  memset(&mBFH, 0, sizeof(mBFH));
-  memset(&mBIH, 0, sizeof(mBIH));
+}
+
+// Constructor for normal BMP files.
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage)
+  : nsBMPDecoder(aImage, State::FILE_HEADER, FILE_HEADER_LENGTH)
+{
+}
+
+// Constructor used for WinBMPv3-ICO files, which lack a file header.
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset)
+  : nsBMPDecoder(aImage, State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH)
+{
+  SetIsWithinICO();
+
+  // Even though the file header isn't present in this case, the dataOffset
+  // field is set as if it is, and so we must increment mPreGapLength
+  // accordingly.
+  mPreGapLength += FILE_HEADER_LENGTH;
+
+  // This is the one piece of data we normally get from a BMP file header, so
+  // it must be provided via an argument.
+  mH.mDataOffset = aDataOffset;
 }
 
 nsBMPDecoder::~nsBMPDecoder()
 {
-  delete[] mColors;
-}
-
-// Obtains the bits per pixel from the internal BIH header.
-int32_t
-nsBMPDecoder::GetBitsPerPixel() const
-{
-  return mBIH.bpp;
-}
-
-// Obtains the width from the internal BIH header.
-int32_t
-nsBMPDecoder::GetWidth() const
-{
-  return mBIH.width;
-}
-
-// Obtains the absolute value of the height from the internal BIH header.
-// If it's positive the bitmap is stored bottom to top, otherwise top to bottom.
-int32_t
-nsBMPDecoder::GetHeight() const
-{
-  return abs(mBIH.height);
-}
-
-// Obtains the internal output image buffer.
-uint32_t*
-nsBMPDecoder::GetImageData()
-{
-  return reinterpret_cast<uint32_t*>(mImageData);
 }
 
 // Obtains the size of the compressed image resource.
 int32_t
 nsBMPDecoder::GetCompressedImageSize() const
 {
-  // In the RGB case image_size might not be set, so compute it manually.
+  // In the RGB case mImageSize might not be set, so compute it manually.
   MOZ_ASSERT(mPixelRowSize != 0);
-  return mBIH.compression == Compression::RGB
-       ? mPixelRowSize * GetHeight()
-       : mBIH.image_size;
+  return mH.mCompression == Compression::RGB
+       ? mPixelRowSize * AbsoluteHeight()
+       : mH.mImageSize;
 }
 
 void
 nsBMPDecoder::FinishInternal()
 {
   // We shouldn't be called in error cases.
   MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!");
 
   // We should never make multiple frames.
   MOZ_ASSERT(GetFrameCount() <= 1, "Multiple BMP frames?");
 
   // Send notifications if appropriate.
   if (!IsMetadataDecode() && HasSize()) {
 
     // Invalidate.
-    nsIntRect r(0, 0, mBIH.width, GetHeight());
+    nsIntRect r(0, 0, mH.mWidth, AbsoluteHeight());
     PostInvalidation(r);
 
     if (mDoesHaveTransparency) {
       MOZ_ASSERT(mMayHaveTransparency);
       PostFrameStop(Opacity::SOME_TRANSPARENCY);
     } else {
       PostFrameStop(Opacity::OPAQUE);
     }
@@ -395,37 +389,37 @@ BitFields::IsR8G8B8() const
 
 uint32_t*
 nsBMPDecoder::RowBuffer()
 {
   if (mDownscaler) {
     return reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer()) + mCurrentPos;
   }
 
-  // Convert from row (1..height) to absolute line (0..height-1).
-  int32_t line = (mBIH.height < 0)
-               ? -mBIH.height - mCurrentRow
+  // Convert from row (1..mHeight) to absolute line (0..mHeight-1).
+  int32_t line = (mH.mHeight < 0)
+               ? -mH.mHeight - mCurrentRow
                : mCurrentRow - 1;
-  int32_t offset = line * mBIH.width + mCurrentPos;
+  int32_t offset = line * mH.mWidth + mCurrentPos;
   return reinterpret_cast<uint32_t*>(mImageData) + offset;
 }
 
 void
 nsBMPDecoder::FinishRow()
 {
   if (mDownscaler) {
     mDownscaler->CommitRow();
 
     if (mDownscaler->HasInvalidation()) {
       DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
       PostInvalidation(invalidRect.mOriginalSizeRect,
                        Some(invalidRect.mTargetSizeRect));
     }
   } else {
-    PostInvalidation(IntRect(0, mCurrentRow, mBIH.width, 1));
+    PostInvalidation(IntRect(0, mCurrentRow, mH.mWidth, 1));
   }
   mCurrentRow--;
 }
 
 void
 nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
@@ -461,165 +455,153 @@ nsBMPDecoder::WriteInternal(const char* 
     return;
   }
 
   MOZ_ASSERT(*terminalState == State::SUCCESS);
 
   return;
 }
 
-// The length of the bihsize field in the info header.
-static const uint32_t BIHSIZE_FIELD_LENGTH = 4;
-
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadFileHeader(const char* aData, size_t aLength)
 {
   mPreGapLength += aLength;
 
-  mBFH.signature[0] = aData[0];
-  mBFH.signature[1] = aData[1];
-  bool signatureOk = mBFH.signature[0] == 'B' && mBFH.signature[1] == 'M';
+  bool signatureOk = aData[0] == 'B' && aData[1] == 'M';
   if (!signatureOk) {
     PostDataError();
     return Transition::Terminate(State::FAILURE);
   }
 
-  // Nb: this field is unreliable. In Windows BMPs it's the file size, but in
-  // OS/2 BMPs it's sometimes the size of the file and info headers. It doesn't
-  // matter because we don't consult it.
-  mBFH.filesize = LittleEndian::readUint32(aData + 2);
+  // We ignore the filesize (aData + 2) and reserved (aData + 6) fields.
 
-  mBFH.reserved = 0;
-
-  mBFH.dataoffset = LittleEndian::readUint32(aData + 10);
+  mH.mDataOffset = LittleEndian::readUint32(aData + 10);
 
   return Transition::To(State::INFO_HEADER_SIZE, BIHSIZE_FIELD_LENGTH);
 }
 
-// We read the info header in two steps: (a) read the bihsize field to
+// We read the info header in two steps: (a) read the mBIHSize field to
 // determine how long the header is; (b) read the rest of the header.
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadInfoHeaderSize(const char* aData, size_t aLength)
 {
   mPreGapLength += aLength;
 
-  mBIH.bihsize = LittleEndian::readUint32(aData);
+  mH.mBIHSize = LittleEndian::readUint32(aData);
 
-  bool bihsizeOk = mBIH.bihsize == InfoHeaderLength::WIN_V2 ||
-                   mBIH.bihsize == InfoHeaderLength::WIN_V3 ||
-                   mBIH.bihsize == InfoHeaderLength::WIN_V4 ||
-                   mBIH.bihsize == InfoHeaderLength::WIN_V5 ||
-                   (mBIH.bihsize >= InfoHeaderLength::OS2_V2_MIN &&
-                    mBIH.bihsize <= InfoHeaderLength::OS2_V2_MAX);
-  if (!bihsizeOk) {
+  bool bihSizeOk = mH.mBIHSize == InfoHeaderLength::WIN_V2 ||
+                   mH.mBIHSize == InfoHeaderLength::WIN_V3 ||
+                   mH.mBIHSize == InfoHeaderLength::WIN_V4 ||
+                   mH.mBIHSize == InfoHeaderLength::WIN_V5 ||
+                   (mH.mBIHSize >= InfoHeaderLength::OS2_V2_MIN &&
+                    mH.mBIHSize <= InfoHeaderLength::OS2_V2_MAX);
+  if (!bihSizeOk) {
     PostDataError();
     return Transition::Terminate(State::FAILURE);
   }
   // ICO BMPs must have a WinVMPv3 header. nsICODecoder should have already
   // terminated decoding if this isn't the case.
-  MOZ_ASSERT_IF(mIsWithinICO, mBIH.bihsize == InfoHeaderLength::WIN_V3);
+  MOZ_ASSERT_IF(mIsWithinICO, mH.mBIHSize == InfoHeaderLength::WIN_V3);
 
   return Transition::To(State::INFO_HEADER_REST,
-                        mBIH.bihsize - BIHSIZE_FIELD_LENGTH);
+                        mH.mBIHSize - BIHSIZE_FIELD_LENGTH);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadInfoHeaderRest(const char* aData, size_t aLength)
 {
   mPreGapLength += aLength;
 
-  // |width| and |height| may be signed (Windows) or unsigned (OS/2). We just
+  // |mWidth| and |mHeight| may be signed (Windows) or unsigned (OS/2). We just
   // read as unsigned because in practice that's good enough.
-  if (mBIH.bihsize == InfoHeaderLength::WIN_V2) {
-    mBIH.width       = LittleEndian::readUint16(aData + 0);
-    mBIH.height      = LittleEndian::readUint16(aData + 2);
-    mBIH.planes      = LittleEndian::readUint16(aData + 4);
-    mBIH.bpp         = LittleEndian::readUint16(aData + 6);
+  if (mH.mBIHSize == InfoHeaderLength::WIN_V2) {
+    mH.mWidth  = LittleEndian::readUint16(aData + 0);
+    mH.mHeight = LittleEndian::readUint16(aData + 2);
+    // We ignore the planes (aData + 4) field; it should always be 1.
+    mH.mBpp    = LittleEndian::readUint16(aData + 6);
   } else {
-    mBIH.width       = LittleEndian::readUint32(aData + 0);
-    mBIH.height      = LittleEndian::readUint32(aData + 4);
-    mBIH.planes      = LittleEndian::readUint16(aData + 8);
-    mBIH.bpp         = LittleEndian::readUint16(aData + 10);
+    mH.mWidth  = LittleEndian::readUint32(aData + 0);
+    mH.mHeight = LittleEndian::readUint32(aData + 4);
+    // We ignore the planes (aData + 4) field; it should always be 1.
+    mH.mBpp    = LittleEndian::readUint16(aData + 10);
 
     // For OS2-BMPv2 the info header may be as little as 16 bytes, so be
     // careful for these fields.
-    mBIH.compression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0;
-    mBIH.image_size  = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0;
-    mBIH.xppm        = aLength >= 24 ? LittleEndian::readUint32(aData + 20) : 0;
-    mBIH.yppm        = aLength >= 28 ? LittleEndian::readUint32(aData + 24) : 0;
-    mBIH.colors      = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0;
-    mBIH.important_colors
-                     = aLength >= 36 ? LittleEndian::readUint32(aData + 32) : 0;
+    mH.mCompression = aLength >= 16 ? LittleEndian::readUint32(aData + 12) : 0;
+    mH.mImageSize   = aLength >= 20 ? LittleEndian::readUint32(aData + 16) : 0;
+    // We ignore the xppm (aData + 20) and yppm (aData + 24) fields.
+    mH.mNumColors   = aLength >= 32 ? LittleEndian::readUint32(aData + 28) : 0;
+    // We ignore the important_colors (aData + 36) field.
 
     // For WinBMPv4, WinBMPv5 and (possibly) OS2-BMPv2 there are additional
     // fields in the info header which we ignore, with the possible exception
     // of the color bitfields (see below).
   }
 
   // Run with NSPR_LOG_MODULES=BMPDecoder:4 set to see this output.
   MOZ_LOG(GetBMPLog(), LogLevel::Debug,
           ("BMP: bihsize=%u, %d x %d, bpp=%u, compression=%u, colors=%u\n",
-          mBIH.bihsize, mBIH.width, mBIH.height, uint32_t(mBIH.bpp),
-          mBIH.compression, mBIH.colors));
+          mH.mBIHSize, mH.mWidth, mH.mHeight, uint32_t(mH.mBpp),
+          mH.mCompression, mH.mNumColors));
 
   // BMPs with negative width are invalid. Also, reject extremely wide images
   // to keep the math sane. And reject INT_MIN as a height because you can't
   // get its absolute value (because -INT_MIN is one more than INT_MAX).
   const int32_t k64KWidth = 0x0000FFFF;
-  bool sizeOk = 0 <= mBIH.width && mBIH.width <= k64KWidth &&
-                mBIH.height != INT_MIN;
+  bool sizeOk = 0 <= mH.mWidth && mH.mWidth <= k64KWidth &&
+                mH.mHeight != INT_MIN;
   if (!sizeOk) {
     PostDataError();
     return Transition::Terminate(State::FAILURE);
   }
 
-  // Check bpp and compression.
+  // Check mBpp and mCompression.
   bool bppCompressionOk =
-    (mBIH.compression == Compression::RGB &&
-      (mBIH.bpp ==  1 || mBIH.bpp ==  4 || mBIH.bpp ==  8 ||
-       mBIH.bpp == 16 || mBIH.bpp == 24 || mBIH.bpp == 32)) ||
-    (mBIH.compression == Compression::RLE8 && mBIH.bpp == 8) ||
-    (mBIH.compression == Compression::RLE4 && mBIH.bpp == 4) ||
-    (mBIH.compression == Compression::BITFIELDS &&
-      (mBIH.bpp == 16 || mBIH.bpp == 32));
+    (mH.mCompression == Compression::RGB &&
+      (mH.mBpp ==  1 || mH.mBpp ==  4 || mH.mBpp ==  8 ||
+       mH.mBpp == 16 || mH.mBpp == 24 || mH.mBpp == 32)) ||
+    (mH.mCompression == Compression::RLE8 && mH.mBpp == 8) ||
+    (mH.mCompression == Compression::RLE4 && mH.mBpp == 4) ||
+    (mH.mCompression == Compression::BITFIELDS &&
+      (mH.mBpp == 16 || mH.mBpp == 32));
   if (!bppCompressionOk) {
     PostDataError();
     return Transition::Terminate(State::FAILURE);
   }
 
   // Post our size to the superclass.
-  uint32_t realHeight = GetHeight();
-  PostSize(mBIH.width, realHeight);
-  mCurrentRow = realHeight;
+  uint32_t absHeight = AbsoluteHeight();
+  PostSize(mH.mWidth, absHeight);
+  mCurrentRow = absHeight;
 
   // Round it up to the nearest byte count, then pad to 4-byte boundary.
   // Compute this even for a metadate decode because GetCompressedImageSize()
   // relies on it.
-  mPixelRowSize = (mBIH.bpp * mBIH.width + 7) / 8;
+  mPixelRowSize = (mH.mBpp * mH.mWidth + 7) / 8;
   uint32_t surplus = mPixelRowSize % 4;
   if (surplus != 0) {
     mPixelRowSize += 4 - surplus;
   }
 
   size_t bitFieldsLengthStillToRead = 0;
-  if (mBIH.compression == Compression::BITFIELDS) {
+  if (mH.mCompression == Compression::BITFIELDS) {
     // Need to read bitfields.
-    if (mBIH.bihsize >= InfoHeaderLength::WIN_V4) {
+    if (mH.mBIHSize >= InfoHeaderLength::WIN_V4) {
       // Bitfields are present in the info header, so we can read them
       // immediately.
       mBitFields.ReadFromHeader(aData + 36, /* aReadAlpha = */ true);
     } else {
       // Bitfields are present after the info header, so we will read them in
       // ReadBitfields().
       bitFieldsLengthStillToRead = BitFields::LENGTH;
     }
-  } else if (mBIH.bpp == 16) {
+  } else if (mH.mBpp == 16) {
     // No bitfields specified; use the default 5-5-5 values.
     mBitFields.SetR5G5B5();
-  } else if (mBIH.bpp == 32) {
+  } else if (mH.mBpp == 32) {
     // No bitfields specified; use the default 8-8-8 values.
     mBitFields.SetR8G8B8();
   }
 
   return Transition::To(State::BITFIELDS, bitFieldsLengthStillToRead);
 }
 
 void
@@ -642,45 +624,45 @@ nsBMPDecoder::ReadBitfields(const char* 
   // in ReadInfoHeader().
   if (aLength != 0) {
     mBitFields.ReadFromHeader(aData, /* aReadAlpha = */ false);
   }
 
   // Note that RLE-encoded BMPs might be transparent because the 'delta' mode
   // can skip pixels and cause implicit transparency.
   mMayHaveTransparency =
-    (mBIH.compression == Compression::RGB && mIsWithinICO && mBIH.bpp == 32) ||
-    mBIH.compression == Compression::RLE8 ||
-    mBIH.compression == Compression::RLE4 ||
-    (mBIH.compression == Compression::BITFIELDS &&
+    (mH.mCompression == Compression::RGB && mIsWithinICO && mH.mBpp == 32) ||
+    mH.mCompression == Compression::RLE8 ||
+    mH.mCompression == Compression::RLE4 ||
+    (mH.mCompression == Compression::BITFIELDS &&
      mBitFields.mAlpha.IsPresent());
   if (mMayHaveTransparency) {
     PostHasTransparency();
   }
 
   // We've now read all the headers. If we're doing a metadata decode, we're
   // done.
   if (IsMetadataDecode()) {
     return Transition::Terminate(State::SUCCESS);
   }
 
   // Set up the color table, if present; it'll be filled in by ReadColorTable().
-  if (mBIH.bpp <= 8) {
-    mNumColors = 1 << mBIH.bpp;
-    if (0 < mBIH.colors && mBIH.colors < mNumColors) {
-      mNumColors = mBIH.colors;
+  if (mH.mBpp <= 8) {
+    mNumColors = 1 << mH.mBpp;
+    if (0 < mH.mNumColors && mH.mNumColors < mNumColors) {
+      mNumColors = mH.mNumColors;
     }
 
     // Always allocate and zero 256 entries, even though mNumColors might be
     // smaller, because the file might erroneously index past mNumColors.
-    mColors = new ColorTableEntry[256];
-    memset(mColors, 0, 256 * sizeof(ColorTableEntry));
+    mColors = MakeUnique<ColorTableEntry[]>(256);
+    memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry));
 
     // OS/2 Bitmaps have no padding byte.
-    mBytesPerColor = (mBIH.bihsize == InfoHeaderLength::WIN_V2) ? 3 : 4;
+    mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4;
   }
 
   MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
   IntSize targetSize = mDownscaler ? mDownscaler->TargetSize() : GetSize();
   nsresult rv = AllocateFrame(/* aFrameNum = */ 0, targetSize,
                               IntRect(IntPoint(), targetSize),
                               SurfaceFormat::B8G8R8A8);
   if (NS_FAILED(rv)) {
@@ -713,49 +695,49 @@ nsBMPDecoder::ReadColorTable(const char*
     // The format is BGR or BGR0.
     mColors[i].mBlue  = uint8_t(aData[0]);
     mColors[i].mGreen = uint8_t(aData[1]);
     mColors[i].mRed   = uint8_t(aData[2]);
     aData += mBytesPerColor;
   }
 
   // We know how many bytes we've read so far (mPreGapLength) and we know the
-  // offset of the pixel data (mBFH.dataoffset), so we can determine the length
+  // offset of the pixel data (mH.mDataOffset), so we can determine the length
   // of the gap (possibly zero) between the color table and the pixel data.
   //
-  // If the gap is negative the file must be malformed (e.g. mBFH.dataoffset
+  // If the gap is negative the file must be malformed (e.g. mH.mDataOffset
   // points into the middle of the color palette instead of past the end) and
   // we give up.
-  if (mPreGapLength > mBFH.dataoffset) {
+  if (mPreGapLength > mH.mDataOffset) {
     PostDataError();
     return Transition::Terminate(State::FAILURE);
   }
-  uint32_t gapLength = mBFH.dataoffset - mPreGapLength;
+  uint32_t gapLength = mH.mDataOffset - mPreGapLength;
   return Transition::To(State::GAP, gapLength);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::SkipGap()
 {
-  bool hasRLE = mBIH.compression == Compression::RLE8 ||
-                mBIH.compression == Compression::RLE4;
+  bool hasRLE = mH.mCompression == Compression::RLE8 ||
+                mH.mCompression == Compression::RLE4;
   return hasRLE
        ? Transition::To(State::RLE_SEGMENT, RLE::SEGMENT_LENGTH)
        : Transition::To(State::PIXEL_ROW, mPixelRowSize);
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadPixelRow(const char* aData)
 {
   MOZ_ASSERT(mCurrentPos == 0);
 
   const uint8_t* src = reinterpret_cast<const uint8_t*>(aData);
   uint32_t* dst = RowBuffer();
-  uint32_t lpos = mBIH.width;
-  switch (mBIH.bpp) {
+  uint32_t lpos = mH.mWidth;
+  switch (mH.mBpp) {
     case 1:
       while (lpos > 0) {
         int8_t bit;
         uint8_t idx;
         for (bit = 7; bit >= 0 && lpos > 0; bit--) {
           idx = (*src >> bit) & 1;
           SetPixel(dst, idx, mColors);
           --lpos;
@@ -812,18 +794,18 @@ nsBMPDecoder::ReadPixelRow(const char* a
       while (lpos > 0) {
         SetPixel(dst, src[2], src[1], src[0]);
         --lpos;
         src += 3;
       }
       break;
 
     case 32:
-      if (mBIH.compression == Compression::RGB && mIsWithinICO &&
-          mBIH.bpp == 32) {
+      if (mH.mCompression == Compression::RGB && mIsWithinICO &&
+          mH.mBpp == 32) {
         // This is a special case only used for 32bpp WinBMPv3-ICO files, which
         // could be in either 0RGB or ARGB format.
         while (lpos > 0) {
           // If src[3] is zero, we can't tell at this point if the image is
           // 0RGB or ARGB. So we just use 0 value as-is. If the image is 0RGB
           // then mDoesHaveTransparency will be false at the end, we'll treat
           // the image as opaque, and the 0 alpha values will be ignored. If
           // the image is ARGB then mDoesHaveTransparency will be true at the
@@ -889,21 +871,21 @@ nsBMPDecoder::ReadRLESegment(const char*
 
   if (byte1 != RLE::ESCAPE) {
     // Encoded mode consists of two bytes: byte1 specifies the number of
     // consecutive pixels to be drawn using the color index contained in
     // byte2.
     //
     // Work around bitmaps that specify too many pixels.
     uint32_t pixelsNeeded =
-      std::min<uint32_t>(mBIH.width - mCurrentPos, byte1);
+      std::min<uint32_t>(mH.mWidth - mCurrentPos, byte1);
     if (pixelsNeeded) {
       uint32_t* dst = RowBuffer();
       mCurrentPos += pixelsNeeded;
-      if (mBIH.compression == Compression::RLE8) {
+      if (mH.mCompression == Compression::RLE8) {
         do {
           SetPixel(dst, byte2, mColors);
           pixelsNeeded --;
         } while (pixelsNeeded);
       } else {
         do {
           Set4BitPixel(dst, byte2, pixelsNeeded, mColors);
         } while (pixelsNeeded);
@@ -929,17 +911,17 @@ nsBMPDecoder::ReadRLESegment(const char*
   }
 
   // Absolute mode. |byte2| gives the number of pixels. The length depends on
   // whether it's 4-bit or 8-bit RLE. Also, the length must be even (and zero
   // padding is used to achieve this when necessary).
   MOZ_ASSERT(mAbsoluteModeNumPixels == 0);
   mAbsoluteModeNumPixels = byte2;
   uint32_t length = byte2;
-  if (mBIH.compression == Compression::RLE4) {
+  if (mH.mCompression == Compression::RLE4) {
     length = (length + 1) / 2;    // halve, rounding up
   }
   if (length & 1) {
     length++;
   }
   return Transition::To(State::RLE_ABSOLUTE, length);
 }
 
@@ -954,18 +936,18 @@ nsBMPDecoder::ReadRLEDelta(const char* a
   if (mDownscaler) {
     // Clear the skipped pixels. (This clears to the end of the row,
     // which is perfect if there's a Y delta and harmless if not).
     mDownscaler->ClearRow(/* aStartingAtCol = */ mCurrentPos);
   }
 
   // Handle the XDelta.
   mCurrentPos += uint8_t(aData[0]);
-  if (mCurrentPos > mBIH.width) {
-    mCurrentPos = mBIH.width;
+  if (mCurrentPos > mH.mWidth) {
+    mCurrentPos = mH.mWidth;
   }
 
   // Handle the Y Delta.
   int32_t yDelta = std::min<int32_t>(uint8_t(aData[1]), mCurrentRow);
   mCurrentRow -= yDelta;
 
   if (mDownscaler && yDelta > 0) {
     // Commit the current row (the first of the skipped rows).
@@ -984,28 +966,28 @@ nsBMPDecoder::ReadRLEDelta(const char* a
 }
 
 LexerTransition<nsBMPDecoder::State>
 nsBMPDecoder::ReadRLEAbsolute(const char* aData, size_t aLength)
 {
   uint32_t n = mAbsoluteModeNumPixels;
   mAbsoluteModeNumPixels = 0;
 
-  if (mCurrentPos + n > uint32_t(mBIH.width)) {
+  if (mCurrentPos + n > uint32_t(mH.mWidth)) {
     // Bad data. Stop decoding; at least part of the image may have been
     // decoded.
     return Transition::Terminate(State::SUCCESS);
   }
 
   // In absolute mode, n represents the number of pixels that follow, each of
   // which contains the color index of a single pixel.
   uint32_t* dst = RowBuffer();
   uint32_t iSrc = 0;
   uint32_t* oldPos = dst;
-  if (mBIH.compression == Compression::RLE8) {
+  if (mH.mCompression == Compression::RLE8) {
     while (n > 0) {
       SetPixel(dst, aData[iSrc], mColors);
       n--;
       iSrc++;
     }
   } else {
     while (n > 0) {
       Set4BitPixel(dst, aData[iSrc], n, mColors);
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -2,26 +2,53 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsBMPDecoder_h
 #define mozilla_image_decoders_nsBMPDecoder_h
 
-#include "BMPFileHeaders.h"
+#include "BMPHeaders.h"
 #include "Decoder.h"
 #include "gfxColor.h"
 #include "StreamingLexer.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace image {
 
 namespace bmp {
 
+/// This struct contains the fields from the file header and info header that
+/// we use during decoding. (Excluding bitfields fields, which are kept in
+/// BitFields.)
+struct Header {
+  uint32_t mDataOffset;     // Offset to raster data.
+  uint32_t mBIHSize;        // Header size.
+  int32_t  mWidth;          // Image width.
+  int32_t  mHeight;         // Image height.
+  uint16_t mBpp;            // Bits per pixel.
+  uint32_t mCompression;    // See struct Compression for valid values.
+  uint32_t mImageSize;      // (compressed) image size. Can be 0 if
+                            // mCompression==0.
+  uint32_t mNumColors;      // Used colors.
+
+  Header()
+   : mDataOffset(0)
+   , mBIHSize(0)
+   , mWidth(0)
+   , mHeight(0)
+   , mBpp(0)
+   , mCompression(0)
+   , mImageSize(0)
+   , mNumColors(0)
+  {}
+};
+
 /// An entry in the color table.
 struct ColorTableEntry {
   uint8_t mRed;
   uint8_t mGreen;
   uint8_t mBlue;
 };
 
 /// All the color-related bitfields for 16bpp and 32bpp images. We use this
@@ -93,33 +120,27 @@ class RasterImage;
 
 /// Decoder for BMP-Files, as used by Windows and OS/2.
 
 class nsBMPDecoder : public Decoder
 {
 public:
   ~nsBMPDecoder();
 
-  /// Obtains the bits per pixel from the internal BIH header.
-  int32_t GetBitsPerPixel() const;
-
-  /// Obtains the width from the internal BIH header.
-  int32_t GetWidth() const;
+  /// Obtains the internal output image buffer.
+  uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); }
 
-  /// Obtains the abs-value of the height from the internal BIH header.
-  int32_t GetHeight() const;
-
-  /// Obtains the internal output image buffer.
-  uint32_t* GetImageData();
+  /// Obtains the length of the internal output image buffer.
   size_t GetImageDataLength() const { return mImageDataLength; }
 
   /// Obtains the size of the compressed image resource.
   int32_t GetCompressedImageSize() const;
 
-  /// Mark this BMP as being within an ICO file.
+  /// Mark this BMP as being within an ICO file. Only used for testing purposes
+  /// because the ICO-specific constructor does this marking automatically.
   void SetIsWithinICO() { mIsWithinICO = true; }
 
   /// Did the BMP file have alpha data of any kind? (Only use this after the
   /// bitmap has been fully decoded.)
   bool HasTransparency() const { return mDoesHaveTransparency; }
 
   /// Force transparency from outside. (Used by the ICO decoder.)
   void SetHasTransparency()
@@ -131,71 +152,79 @@ public:
   virtual void WriteInternal(const char* aBuffer,
                              uint32_t aCount) override;
   virtual void FinishInternal() override;
 
 private:
   friend class DecoderFactory;
   friend class nsICODecoder;
 
-  // Decoders should only be instantiated via DecoderFactory.
-  // XXX(seth): nsICODecoder is temporarily an exception to this rule.
-  explicit nsBMPDecoder(RasterImage* aImage);
-
-  uint32_t* RowBuffer();
-
-  void FinishRow();
-
   enum class State {
     FILE_HEADER,
     INFO_HEADER_SIZE,
     INFO_HEADER_REST,
     BITFIELDS,
     COLOR_TABLE,
     GAP,
     PIXEL_ROW,
     RLE_SEGMENT,
     RLE_DELTA,
     RLE_ABSOLUTE,
     SUCCESS,
     FAILURE
   };
 
+  // This is the constructor used by DecoderFactory.
+  explicit nsBMPDecoder(RasterImage* aImage);
+
+  // This is the constructor used by nsICODecoder.
+  // XXX(seth): nsICODecoder is temporarily an exception to the rule that
+  //            decoders should only be instantiated via DecoderFactory.
+  nsBMPDecoder(RasterImage* aImage, uint32_t aDataOffset);
+
+  // Helper constructor called by the other two.
+  nsBMPDecoder(RasterImage* aImage, State aState, size_t aLength);
+
+  int32_t AbsoluteHeight() const { return abs(mH.mHeight); }
+
+  uint32_t* RowBuffer();
+
+  void FinishRow();
+
   LexerTransition<State> ReadFileHeader(const char* aData, size_t aLength);
   LexerTransition<State> ReadInfoHeaderSize(const char* aData, size_t aLength);
   LexerTransition<State> ReadInfoHeaderRest(const char* aData, size_t aLength);
   LexerTransition<State> ReadBitfields(const char* aData, size_t aLength);
   LexerTransition<State> ReadColorTable(const char* aData, size_t aLength);
   LexerTransition<State> SkipGap();
   LexerTransition<State> ReadPixelRow(const char* aData);
   LexerTransition<State> ReadRLESegment(const char* aData);
   LexerTransition<State> ReadRLEDelta(const char* aData);
   LexerTransition<State> ReadRLEAbsolute(const char* aData, size_t aLength);
 
   StreamingLexer<State> mLexer;
 
-  bmp::FileHeader mBFH;
-  bmp::V5InfoHeader mBIH;
+  bmp::Header mH;
 
   // If the BMP is within an ICO file our treatment of it differs slightly.
   bool mIsWithinICO;
 
   bmp::BitFields mBitFields;
 
   // Might the image have transparency? Determined from the headers during
   // metadata decode. (Does not guarantee the image actually has transparency.)
   bool mMayHaveTransparency;
 
   // Does the image have transparency? Determined during full decoding, so only
   // use this after that has been completed.
   bool mDoesHaveTransparency;
 
   uint32_t mNumColors;      // The number of used colors, i.e. the number of
                             // entries in mColors, if it's present.
-  bmp::ColorTableEntry* mColors; // The color table, if it's present.
+  UniquePtr<bmp::ColorTableEntry[]> mColors; // The color table, if it's present.
   uint32_t mBytesPerColor;  // 3 or 4, depending on the format
 
   // The number of bytes prior to the optional gap that have been read. This
   // is used to find the start of the pixel data.
   uint32_t mPreGapLength;
 
   uint32_t mPixelRowSize;   // The number of bytes per pixel row.
 
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -16,17 +16,17 @@
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
 
 // Constants.
 static const uint32_t ICOHEADERSIZE = 6;
-static const uint32_t BITMAPINFOSIZE = 40;
+static const uint32_t BITMAPINFOSIZE = bmp::InfoHeaderLength::WIN_ICO;
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
 
 uint32_t
 nsICODecoder::CalcAlphaRowSize()
 {
@@ -105,63 +105,26 @@ nsICODecoder::GetFinalStateFromContained
   mDecodeAborted = mContainedDecoder->WasAborted();
   mProgress |= mContainedDecoder->TakeProgress();
   mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
   mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
 
   MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete());
 }
 
-// Returns a buffer filled with the bitmap file header in little endian:
-// Signature 2 bytes 'BM'
-// FileSize      4 bytes File size in bytes
-// reserved      4 bytes unused (=0)
-// DataOffset    4 bytes File offset to Raster Data
-// Returns true if successful
-bool
-nsICODecoder::FillBitmapFileHeaderBuffer(int8_t* bfh)
-{
-  memset(bfh, 0, 14);
-  bfh[0] = 'B';
-  bfh[1] = 'M';
-  int32_t dataOffset = 0;
-  int32_t fileSize = 0;
-  dataOffset = bmp::FileHeader::LENGTH + BITMAPINFOSIZE;
-
-  // The color table is present only if BPP is <= 8
-  if (mDirEntry.mBitCount <= 8) {
-    uint16_t numColors = GetNumColors();
-    if (numColors == (uint16_t)-1) {
-      return false;
-    }
-    dataOffset += 4 * numColors;
-    fileSize = dataOffset + GetRealWidth() * GetRealHeight();
-  } else {
-    fileSize = dataOffset + (mDirEntry.mBitCount * GetRealWidth() *
-                             GetRealHeight()) / 8;
-  }
-
-  NativeEndian::swapToLittleEndianInPlace(&fileSize, 1);
-  memcpy(bfh + 2, &fileSize, sizeof(fileSize));
-  NativeEndian::swapToLittleEndianInPlace(&dataOffset, 1);
-  memcpy(bfh + 10, &dataOffset, sizeof(dataOffset));
-  return true;
-}
-
 // A BMP inside of an ICO has *2 height because of the AND mask
 // that follows the actual bitmap.  The BMP shouldn't know about
 // this difference though.
 bool
 nsICODecoder::FixBitmapHeight(int8_t* bih)
 {
-  // Get the height from the BMP file information header
-  int32_t height;
-  memcpy(&height, bih + 8, sizeof(height));
-  NativeEndian::swapFromLittleEndianInPlace(&height, 1);
-  // BMPs can be stored inverted by having a negative height
+  // Get the height from the BMP file information header.
+  int32_t height = LittleEndian::readInt32(bih + 8);
+
+  // BMPs can be stored inverted by having a negative height.
   height = abs(height);
 
   // The bitmap height is by definition * 2 what it should be to account for
   // the 'AND mask'. It is * 2 even if the `AND mask` is not present.
   height /= 2;
 
   if (height > 256) {
     return false;
@@ -171,78 +134,53 @@ nsICODecoder::FixBitmapHeight(int8_t* bi
   // the ICO height.  So fix the ICO height.
   if (height == 256) {
     mDirEntry.mHeight = 0;
   } else {
     mDirEntry.mHeight = (int8_t)height;
   }
 
   // Fix the BMP height in the BIH so that the BMP decoder can work properly
-  NativeEndian::swapToLittleEndianInPlace(&height, 1);
-  memcpy(bih + 8, &height, sizeof(height));
+  LittleEndian::writeInt32(bih + 8, height);
   return true;
 }
 
 // We should always trust the contained resource for the width
 // information over our own information.
 bool
 nsICODecoder::FixBitmapWidth(int8_t* bih)
 {
-  // Get the width from the BMP file information header
-  int32_t width;
-  memcpy(&width, bih + 4, sizeof(width));
-  NativeEndian::swapFromLittleEndianInPlace(&width, 1);
+  // Get the width from the BMP file information header.
+  int32_t width = LittleEndian::readInt32(bih + 4);
+
   if (width > 256) {
     return false;
   }
 
-  // We should always trust the width  from the bitmap itself instead of
+  // We should always trust the width from the bitmap itself instead of
   // the ICO width.
   if (width == 256) {
     mDirEntry.mWidth = 0;
   } else {
     mDirEntry.mWidth = (int8_t)width;
   }
   return true;
 }
 
-// The BMP information header's bits per pixel should be trusted
-// more than what we have.  Usually the ICO's BPP is set to 0.
-int32_t
-nsICODecoder::ReadBPP(const char* aBIH)
-{
-  const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
-  int32_t bitsPerPixel;
-  memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
-  NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
-  return bitsPerPixel;
-}
-
-int32_t
-nsICODecoder::ReadBIHSize(const char* aBIH)
-{
-  const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
-  int32_t headerSize;
-  memcpy(&headerSize, bih, sizeof(headerSize));
-  NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
-  return headerSize;
-}
-
 LexerTransition<ICOState>
 nsICODecoder::ReadHeader(const char* aData)
 {
   // If the third byte is 1, this is an icon. If 2, a cursor.
   if ((aData[2] != 1) && (aData[2] != 2)) {
     return Transition::Terminate(ICOState::FAILURE);
   }
   mIsCursor = (aData[2] == 2);
 
   // The fifth and sixth bytes specify the number of resources in the file.
-  mNumIcons =
-    LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aData + 4));
+  mNumIcons = LittleEndian::readUint16(aData + 4);
   if (mNumIcons == 0) {
     return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
   }
 
   // Downscale-during-decode can end up decoding different resources in the ICO
   // file depending on the target size. Since the resources are not necessarily
   // scaled versions of the same image, some may be transparent and some may not
   // be. We could be precise about transparency if we decoded the metadata of
@@ -266,29 +204,24 @@ nsICODecoder::FirstResourceOffset() cons
 
 LexerTransition<ICOState>
 nsICODecoder::ReadDirEntry(const char* aData)
 {
   mCurrIcon++;
 
   // Read the directory entry.
   IconDirEntry e;
-  memset(&e, 0, sizeof(e));
-  memcpy(&e.mWidth, aData, sizeof(e.mWidth));
-  memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight));
-  memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount));
-  memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved));
-  memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes));
-  e.mPlanes = LittleEndian::readUint16(&e.mPlanes);
-  memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount));
-  e.mBitCount = LittleEndian::readUint16(&e.mBitCount);
-  memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes));
-  e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes);
-  memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset));
-  e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset);
+  e.mWidth       = aData[0];
+  e.mHeight      = aData[1];
+  e.mColorCount  = aData[2];
+  e.mReserved    = aData[3];
+  e.mPlanes      = LittleEndian::readUint16(aData + 4);
+  e.mBitCount    = LittleEndian::readUint16(aData + 6);
+  e.mBytesInRes  = LittleEndian::readUint32(aData + 8);
+  e.mImageOffset = LittleEndian::readUint32(aData + 12);
 
   // Determine if this is the biggest resource we've seen so far. We always use
   // the biggest resource for the intrinsic size, and if we're not downscaling,
   // we select it as the best resource as well.
   IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
   if (e.mBitCount >= mBiggestResourceColorDepth &&
       entrySize.width * entrySize.height >=
         mBiggestResourceSize.width * mBiggestResourceSize.height) {
@@ -384,31 +317,18 @@ nsICODecoder::SniffResource(const char* 
     }
 
     // Read in the rest of the PNG unbuffered.
     size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
     return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
                                     ICOState::READ_PNG,
                                     toRead);
   } else {
-    // Create a BMP decoder which will do most of the work for us; the exception
-    // is the AND mask, which isn't present in standalone BMPs.
-    nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
-    mContainedDecoder = bmpDecoder;
-    bmpDecoder->SetIsWithinICO();
-    mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
-    mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
-    mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
-    if (mDownscaler) {
-      mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
-    }
-    mContainedDecoder->Init();
-
     // Make sure we have a sane size for the bitmap information header.
-    int32_t bihSize = ReadBIHSize(aData);
+    int32_t bihSize = LittleEndian::readUint32(aData);
     if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
       return Transition::Terminate(ICOState::FAILURE);
     }
 
     // Buffer the first part of the bitmap information header.
     memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
 
     // Read in the rest of the bitmap information header.
@@ -434,32 +354,45 @@ nsICODecoder::ReadPNG(const char* aData,
 }
 
 LexerTransition<ICOState>
 nsICODecoder::ReadBIH(const char* aData)
 {
   // Buffer the rest of the bitmap information header.
   memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
 
-  // Extracting the BPP from the BIH header; it should be trusted over the one
-  // we have from the ICO header.
-  mBPP = ReadBPP(mBIHraw);
+  // Extract the BPP from the BIH header; it should be trusted over the one
+  // we have from the ICO header which is usually set to 0.
+  mBPP = LittleEndian::readUint16(mBIHraw + 14);
 
   // The ICO format when containing a BMP does not include the 14 byte
-  // bitmap file header. To use the code of the BMP decoder we need to
-  // generate this header ourselves and feed it to the BMP decoder.
-  int8_t bfhBuffer[BMPFILEHEADERSIZE];
-  if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
-    return Transition::Terminate(ICOState::FAILURE);
+  // bitmap file header. So we create the BMP decoder via the constructor that
+  // tells it to skip this, and pass in the required data (dataOffset) that
+  // would have been present in the header.
+  uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE;
+  if (mDirEntry.mBitCount <= 8) {
+    // The color table is present only if BPP is <= 8.
+    uint16_t numColors = GetNumColors();
+    if (numColors == (uint16_t)-1) {
+      return Transition::Terminate(ICOState::FAILURE);
+    }
+    dataOffset += 4 * numColors;
   }
 
-  if (!WriteToContainedDecoder(reinterpret_cast<const char*>(bfhBuffer),
-                               sizeof(bfhBuffer))) {
-    return Transition::Terminate(ICOState::FAILURE);
+  // Create a BMP decoder which will do most of the work for us; the exception
+  // is the AND mask, which isn't present in standalone BMPs.
+  RefPtr<nsBMPDecoder> bmpDecoder = new nsBMPDecoder(mImage, dataOffset);
+  mContainedDecoder = bmpDecoder;
+  mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
+  mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
+  mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
+  if (mDownscaler) {
+    mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
   }
+  mContainedDecoder->Init();
 
   // Fix the ICO height from the BIH. It needs to be halved so our BMP decoder
   // will understand, because the BMP decoder doesn't expect the alpha mask that
   // follows the BMP data in an ICO.
   if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
@@ -468,24 +401,16 @@ nsICODecoder::ReadBIH(const char* aData)
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   // Write out the BMP's bitmap info header.
   if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
-  // Sometimes the ICO BPP header field is not filled out so we should trust the
-  // contained resource over our own information.
-  // XXX(seth): Is this ever different than the value we obtained from
-  // ReadBPP() above?
-  RefPtr<nsBMPDecoder> bmpDecoder =
-    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
-  mBPP = bmpDecoder->GetBitsPerPixel();
-
   // Check to make sure we have valid color settings.
   uint16_t numColors = GetNumColors();
   if (numColors == uint16_t(-1)) {
     return Transition::Terminate(ICOState::FAILURE);
   }
 
   // Do we have an AND mask on this BMP? If so, we need to read it after we read
   // the BMP data itself.
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -82,30 +82,24 @@ private:
 
   // Writes to the contained decoder and sets the appropriate errors
   // Returns true if there are no errors.
   bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
 
   // Gets decoder state from the contained decoder so it's visible externally.
   void GetFinalStateFromContainedDecoder();
 
-  // Creates a bitmap file header buffer, returns true if successful
-  bool FillBitmapFileHeaderBuffer(int8_t* bfh);
   // Fixes the ICO height to match that of the BIH.
   // and also fixes the BIH height to be /2 of what it was.
   // See definition for explanation.
   // Returns false if invalid information is contained within.
   bool FixBitmapHeight(int8_t* bih);
   // Fixes the ICO width to match that of the BIH.
   // Returns false if invalid information is contained within.
   bool FixBitmapWidth(int8_t* bih);
-  // Extract bitmap info header size count from BMP information header
-  int32_t ReadBIHSize(const char* aBIH);
-  // Extract bit count from BMP information header
-  int32_t ReadBPP(const char* aBIH);
   // Calculates the row size in bytes for the AND mask table
   uint32_t CalcAlphaRowSize();
   // Obtains the number of colors from the BPP, mBPP must be filled in
   uint16_t GetNumColors();
 
   LexerTransition<ICOState> ReadHeader(const char* aData);
   LexerTransition<ICOState> ReadDirEntry(const char* aData);
   LexerTransition<ICOState> SniffResource(const char* aData);
@@ -115,17 +109,17 @@ private:
   LexerTransition<ICOState> PrepareForMask();
   LexerTransition<ICOState> ReadMaskRow(const char* aData);
   LexerTransition<ICOState> FinishMask();
   LexerTransition<ICOState> FinishResource();
 
   StreamingLexer<ICOState, 32> mLexer; // The lexer.
   RefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
   UniquePtr<uint8_t[]> mMaskBuffer;    // A temporary buffer for the alpha mask.
-  char mBIHraw[40];                    // The bitmap information header.
+  char mBIHraw[bmp::InfoHeaderLength::WIN_ICO]; // The bitmap information header.
   IconDirEntry mDirEntry;              // The dir entry for the selected resource.
   IntSize mBiggestResourceSize;        // Used to select the intrinsic size.
   IntSize mBiggestResourceHotSpot;     // Used to select the intrinsic size.
   uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
   int32_t mBestResourceDelta;          // Used to select the best resource.
   uint16_t mBestResourceColorDepth;    // Used to select the best resource.
   uint16_t mNumIcons; // Stores the number of icons in the ICO file.
   uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
--- a/image/encoders/bmp/nsBMPEncoder.cpp
+++ b/image/encoders/bmp/nsBMPEncoder.cpp
@@ -482,19 +482,19 @@ void
 nsBMPEncoder::InitFileHeader(Version aVersion, uint32_t aBPP, uint32_t aWidth,
                              uint32_t aHeight)
 {
   memset(&mBMPFileHeader, 0, sizeof(mBMPFileHeader));
   mBMPFileHeader.signature[0] = 'B';
   mBMPFileHeader.signature[1] = 'M';
 
   if (aVersion == VERSION_3) {
-    mBMPFileHeader.dataoffset = FileHeader::LENGTH + InfoHeaderLength::WIN_V3;
+    mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V3;
   } else { // aVersion == 5
-    mBMPFileHeader.dataoffset = FileHeader::LENGTH + InfoHeaderLength::WIN_V5;
+    mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V5;
   }
 
   // The color table is present only if BPP is <= 8
   if (aBPP <= 8) {
     uint32_t numColors = 1 << aBPP;
     mBMPFileHeader.dataoffset += 4 * numColors;
     mBMPFileHeader.filesize = mBMPFileHeader.dataoffset + aWidth * aHeight;
   } else {
--- a/image/encoders/bmp/nsBMPEncoder.h
+++ b/image/encoders/bmp/nsBMPEncoder.h
@@ -6,28 +6,84 @@
 #ifndef mozilla_image_encoders_bmp_nsBMPEncoder_h
 #define mozilla_image_encoders_bmp_nsBMPEncoder_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/UniquePtr.h"
 
 #include "imgIEncoder.h"
-#include "BMPFileHeaders.h"
+#include "BMPHeaders.h"
 
 #include "nsCOMPtr.h"
 
 #define NS_BMPENCODER_CID \
 { /* 13a5320c-4c91-4FA4-bd16-b081a3ba8c0b */         \
      0x13a5320c,                                     \
      0x4c91,                                         \
      0x4fa4,                                         \
     {0xbd, 0x16, 0xb0, 0x81, 0xa3, 0Xba, 0x8c, 0x0b} \
 }
 
+namespace mozilla {
+namespace image {
+namespace bmp {
+
+struct FileHeader {
+  char signature[2];   // String "BM".
+  uint32_t filesize;   // File size.
+  int32_t reserved;    // Zero.
+  uint32_t dataoffset; // Offset to raster data.
+};
+
+struct XYZ {
+  int32_t x, y, z;
+};
+
+struct XYZTriple {
+  XYZ r, g, b;
+};
+
+struct V5InfoHeader {
+  uint32_t bihsize;          // Header size
+  int32_t width;             // Uint16 in OS/2 BMPs
+  int32_t height;            // Uint16 in OS/2 BMPs
+  uint16_t planes;           // =1
+  uint16_t bpp;              // Bits per pixel.
+  uint32_t compression;      // See Compression for valid values
+  uint32_t image_size;       // (compressed) image size. Can be 0 if
+                             // compression==0
+  uint32_t xppm;             // Pixels per meter, horizontal
+  uint32_t yppm;             // Pixels per meter, vertical
+  uint32_t colors;           // Used Colors
+  uint32_t important_colors; // Number of important colors. 0=all
+  // The rest of the header is not available in WIN_V3 BMP Files
+  uint32_t red_mask;         // Bits used for red component
+  uint32_t green_mask;       // Bits used for green component
+  uint32_t blue_mask;        // Bits used for blue component
+  uint32_t alpha_mask;       // Bits used for alpha component
+  uint32_t color_space;      // 0x73524742=LCS_sRGB ...
+  // These members are unused unless color_space == LCS_CALIBRATED_RGB
+  XYZTriple white_point;     // Logical white point
+  uint32_t gamma_red;        // Red gamma component
+  uint32_t gamma_green;      // Green gamma component
+  uint32_t gamma_blue;       // Blue gamma component
+  uint32_t intent;           // Rendering intent
+  // These members are unused unless color_space == LCS_PROFILE_*
+  uint32_t profile_offset;   // Offset to profile data in bytes
+  uint32_t profile_size;     // Size of profile data in bytes
+  uint32_t reserved;         // =0
+
+  static const uint32_t COLOR_SPACE_LCS_SRGB = 0x73524742;
+};
+
+} // namespace bmp
+} // namespace image
+} // namespace mozilla
+
 // Provides BMP encoding functionality. Use InitFromData() to do the
 // encoding. See that function definition for encoding options.
 
 class nsBMPEncoder final : public imgIEncoder
 {
   typedef mozilla::ReentrantMonitor ReentrantMonitor;
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
--- a/image/encoders/ico/nsICOEncoder.cpp
+++ b/image/encoders/ico/nsICOEncoder.cpp
@@ -157,36 +157,36 @@ nsICOEncoder::AddImageFrame(const uint8_
     mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
     if (!mImageBufferStart) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     mImageBufferCurr = mImageBufferStart;
 
     // Icon files that wrap a BMP file must not include the BITMAPFILEHEADER
     // section at the beginning of the encoded BMP data, so we must skip over
-    // bmp::FileHeader::LENGTH bytes when adding the BMP content to the icon
+    // bmp::FILE_HEADER_LENGTH bytes when adding the BMP content to the icon
     // file.
     mICODirEntry.mBytesInRes =
-      BMPImageBufferSize - bmp::FileHeader::LENGTH + andMaskSize;
+      BMPImageBufferSize - bmp::FILE_HEADER_LENGTH + andMaskSize;
 
     // Encode the icon headers
     EncodeFileHeader();
     EncodeInfoHeader();
 
     char* imageBuffer;
     rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
     NS_ENSURE_SUCCESS(rv, rv);
-    memcpy(mImageBufferCurr, imageBuffer + bmp::FileHeader::LENGTH,
-           BMPImageBufferSize - bmp::FileHeader::LENGTH);
+    memcpy(mImageBufferCurr, imageBuffer + bmp::FILE_HEADER_LENGTH,
+           BMPImageBufferSize - bmp::FILE_HEADER_LENGTH);
     // We need to fix the BMP height to be *2 for the AND mask
     uint32_t fixedHeight = GetRealHeight() * 2;
     NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1);
     // The height is stored at an offset of 8 from the DIB header
     memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight));
-    mImageBufferCurr += BMPImageBufferSize - bmp::FileHeader::LENGTH;
+    mImageBufferCurr += BMPImageBufferSize - bmp::FILE_HEADER_LENGTH;
 
     // Calculate rowsize in DWORD's
     uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
     int32_t currentLine = GetRealHeight();
 
     // Write out the AND mask
     while (currentLine > 0) {
       currentLine--;
--- a/image/encoders/jpeg/nsJPEGEncoder.cpp
+++ b/image/encoders/jpeg/nsJPEGEncoder.cpp
@@ -157,29 +157,29 @@ nsJPEGEncoder::InitFromData(const uint8_
 
   // feed it the rows
   if (aInputFormat == INPUT_FORMAT_RGB) {
     while (cinfo.next_scanline < cinfo.image_height) {
       const uint8_t* row = &aData[cinfo.next_scanline * aStride];
       jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1);
     }
   } else if (aInputFormat == INPUT_FORMAT_RGBA) {
-    uint8_t* row = new uint8_t[aWidth * 3];
+    UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3);
+    uint8_t* row = rowptr.get();
     while (cinfo.next_scanline < cinfo.image_height) {
       ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth);
       jpeg_write_scanlines(&cinfo, &row, 1);
     }
-    delete[] row;
   } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
-    uint8_t* row = new uint8_t[aWidth * 3];
+    UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3);
+    uint8_t* row = rowptr.get();
     while (cinfo.next_scanline < cinfo.image_height) {
       ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth);
       jpeg_write_scanlines(&cinfo, &row, 1);
     }
-    delete[] row;
   }
 
   jpeg_finish_compress(&cinfo);
   jpeg_destroy_compress(&cinfo);
 
   mFinished = true;
   NotifyListener();
 
--- a/image/encoders/png/nsPNGEncoder.cpp
+++ b/image/encoders/png/nsPNGEncoder.cpp
@@ -282,32 +282,28 @@ nsPNGEncoder::AddImageFrame(const uint8_
   png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE);
 #endif
 
   // write each row: if we add more input formats, we may want to
   // generalize the conversions
   if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
     // PNG requires RGBA with post-multiplied alpha, so we need to
     // convert
-    uint8_t* row = new uint8_t[aWidth * 4];
+    UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4);
     for (uint32_t y = 0; y < aHeight; y++) {
-      ConvertHostARGBRow(&aData[y * aStride], row, aWidth, useTransparency);
-      png_write_row(mPNG, row);
+      ConvertHostARGBRow(&aData[y * aStride], row.get(), aWidth, useTransparency);
+      png_write_row(mPNG, row.get());
     }
-    delete[] row;
-
   } else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) {
     // RBGA, but we need to strip the alpha
-    uint8_t* row = new uint8_t[aWidth * 4];
+    UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4);
     for (uint32_t y = 0; y < aHeight; y++) {
-      StripAlpha(&aData[y * aStride], row, aWidth);
-      png_write_row(mPNG, row);
+      StripAlpha(&aData[y * aStride], row.get(), aWidth);
+      png_write_row(mPNG, row.get());
     }
-    delete[] row;
-
   } else if (aInputFormat == INPUT_FORMAT_RGB ||
              aInputFormat == INPUT_FORMAT_RGBA) {
     // simple RBG(A), no conversion needed
     for (uint32_t y = 0; y < aHeight; y++) {
       png_write_row(mPNG, (uint8_t*)&aData[y * aStride]);
     }
 
   } else {
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -319,16 +319,24 @@ typedef bool
 /**
  * The old-style JSClass.enumerate op should define all lazy properties not
  * yet reflected in obj.
  */
 typedef bool
 (* JSEnumerateOp)(JSContext* cx, JS::HandleObject obj);
 
 /**
+ * The type of ObjectOps::funToString.  This callback allows an object to
+ * provide a custom string to use when Function.prototype.toString is invoked on
+ * that object.  A null return value means OOM.
+ */
+typedef JSString*
+(* JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, unsigned indent);
+
+/**
  * Resolve a lazy property named by id in obj by defining it directly in obj.
  * Lazy properties are those reflected from some peer native property space
  * (e.g., the DOM attributes for a given node reflected as obj) on demand.
  *
  * JS looks for a property in an object, and if not found, tries to resolve
  * the given id. *resolvedp should be set to true iff the property was
  * was defined on |obj|.
  */
@@ -645,32 +653,33 @@ struct ObjectOps
     SetPropertyOp       setProperty;
     GetOwnPropertyOp    getOwnPropertyDescriptor;
     DeletePropertyOp    deleteProperty;
     WatchOp             watch;
     UnwatchOp           unwatch;
     GetElementsOp       getElements;
     JSNewEnumerateOp    enumerate;
     ThisValueOp         thisValue;
+    JSFunToStringOp     funToString;
 };
 
 #define JS_NULL_OBJECT_OPS                                                    \
     {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,  \
      nullptr, nullptr, nullptr, nullptr}
 
 } // namespace js
 
 // Classes, objects, and properties.
 
 typedef void (*JSClassInternal)();
 
 struct JSClass {
     JS_CLASS_MEMBERS(JSFinalizeOp);
 
-    void*               reserved[25];
+    void*               reserved[26];
 };
 
 #define JSCLASS_HAS_PRIVATE             (1<<0)  // objects have private slot
 #define JSCLASS_DELAY_METADATA_CALLBACK (1<<1)  // class's initialization code
                                                 // will call
                                                 // SetNewObjectMetadata itself
 #define JSCLASS_PRIVATE_IS_NSISUPPORTS  (1<<3)  // private is (nsISupports*)
 #define JSCLASS_IS_DOMJSCLASS           (1<<4)  // objects are DOM
--- a/js/src/asmjs/AsmJSFrameIterator.cpp
+++ b/js/src/asmjs/AsmJSFrameIterator.cpp
@@ -131,17 +131,17 @@ static const unsigned PushedRetAddr = 4;
 static const unsigned PushedFP = 16;
 static const unsigned StoredFP = 20;
 static const unsigned PostStorePrePopFP = 4;
 #elif defined(JS_CODEGEN_ARM64)
 static const unsigned PushedRetAddr = 0;
 static const unsigned PushedFP = 0;
 static const unsigned StoredFP = 0;
 static const unsigned PostStorePrePopFP = 0;
-#elif defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
 static const unsigned PushedRetAddr = 8;
 static const unsigned PushedFP = 24;
 static const unsigned StoredFP = 28;
 static const unsigned PostStorePrePopFP = 4;
 #elif defined(JS_CODEGEN_NONE)
 # if defined(DEBUG)
 static const unsigned PushedRetAddr = 0;
 static const unsigned PostStorePrePopFP = 0;
@@ -152,17 +152,17 @@ static const unsigned StoredFP = 1;
 # error "Unknown architecture!"
 #endif
 
 static void
 PushRetAddr(MacroAssembler& masm)
 {
 #if defined(JS_CODEGEN_ARM)
     masm.push(lr);
-#elif defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     masm.push(ra);
 #else
     // The x86/x64 call instruction pushes the return address.
 #endif
 }
 
 // Generate a prologue that maintains AsmJSActivation::fp as the virtual frame
 // pointer so that AsmJSProfilingFrameIterator can walk the stack at any pc in
@@ -216,17 +216,18 @@ GenerateProfilingPrologue(MacroAssembler
 }
 
 // Generate the inverse of GenerateProfilingPrologue.
 static void
 GenerateProfilingEpilogue(MacroAssembler& masm, unsigned framePushed, AsmJSExit::Reason reason,
                           Label* profilingReturn)
 {
     Register scratch = ABIArgGenerator::NonReturn_VolatileReg0;
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
+    defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     Register scratch2 = ABIArgGenerator::NonReturn_VolatileReg1;
 #endif
 
     if (framePushed)
         masm.addToStackPtr(Imm32(framePushed));
 
     masm.loadAsmJSActivation(scratch);
 
@@ -240,17 +241,18 @@ GenerateProfilingEpilogue(MacroAssembler
 #if defined(JS_CODEGEN_ARM)
         AutoForbidPools afp(&masm, /* number of instructions in scope = */ 4);
 #endif
 
         // sp protects the stack from clobber via asynchronous signal handlers
         // and the async interrupt exit. Since activation.fp can be read at any
         // time and still points to the current frame, be careful to only update
         // sp after activation.fp has been repointed to the caller's frame.
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
+    defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
         masm.loadPtr(Address(masm.getStackPointer(), 0), scratch2);
         masm.storePtr(scratch2, Address(scratch, AsmJSActivation::offsetOfFP()));
         DebugOnly<uint32_t> prePop = masm.currentOffset();
         masm.addToStackPtr(Imm32(sizeof(void *)));
         MOZ_ASSERT_IF(!masm.oom(), PostStorePrePopFP == masm.currentOffset() - prePop);
 #else
         masm.pop(Address(scratch, AsmJSActivation::offsetOfFP()));
         MOZ_ASSERT(PostStorePrePopFP == 0);
@@ -339,16 +341,23 @@ js::GenerateAsmJSFunctionEpilogue(MacroA
         masm.twoByteNop();
 #elif defined(JS_CODEGEN_ARM)
         masm.nop();
 #elif defined(JS_CODEGEN_MIPS32)
         masm.nop();
         masm.nop();
         masm.nop();
         masm.nop();
+#elif defined(JS_CODEGEN_MIPS64)
+        masm.nop();
+        masm.nop();
+        masm.nop();
+        masm.nop();
+        masm.nop();
+        masm.nop();
 #endif
     }
 
     // Normal epilogue:
     masm.addToStackPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress));
     masm.ret();
     masm.setFramePushed(0);
 
@@ -561,17 +570,17 @@ AsmJSProfilingFrameIterator::AsmJSProfil
         // innermost call. To avoid this problem, we use the static structure of
         // the code in the prologue and epilogue to do the Right Thing.
         uint32_t offsetInModule = (uint8_t*)state.pc - module_->codeBase();
         MOZ_ASSERT(offsetInModule < module_->codeBytes());
         MOZ_ASSERT(offsetInModule >= codeRange->begin());
         MOZ_ASSERT(offsetInModule < codeRange->end());
         uint32_t offsetInCodeRange = offsetInModule - codeRange->begin();
         void** sp = (void**)state.sp;
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
         if (offsetInCodeRange < PushedRetAddr) {
             // First instruction of the ARM/MIPS function; the return address is
             // still in lr and fp still holds the caller's fp.
             callerPC_ = state.lr;
             callerFP_ = fp;
             AssertMatchesCallSite(*module_, codeRange, callerPC_, callerFP_, sp - 2);
         } else if (offsetInModule == codeRange->profilingReturn() - PostStorePrePopFP) {
             // Second-to-last instruction of the ARM/MIPS function; fp points to
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -379,16 +379,27 @@ AsmJSModule::finish(ExclusiveContext* cx
     for (size_t i = 0; i < masm.numLongJumps(); i++) {
         RelativeLink link(RelativeLink::InstructionImmediate);
         link.patchAtOffset = masm.longJump(i);
         InstImm* inst = (InstImm*)(code_ + masm.longJump(i));
         link.targetOffset = Assembler::ExtractLuiOriValue(inst, inst->next()) - (uint32_t)code_;
         if (!staticLinkData_.relativeLinks.append(link))
             return false;
     }
+#elif defined(JS_CODEGEN_MIPS64)
+    // On MIPS64 we need to update all the long jumps because they contain an
+    // absolute adress.
+    for (size_t i = 0; i < masm.numLongJumps(); i++) {
+        RelativeLink link(RelativeLink::InstructionImmediate);
+        link.patchAtOffset = masm.longJump(i);
+        InstImm* inst = (InstImm*)(code_ + masm.longJump(i));
+        link.targetOffset = Assembler::ExtractLoad64Value(inst) - (uint64_t)code_;
+        if (!staticLinkData_.relativeLinks.append(link))
+            return false;
+    }
 #endif
 
 #if defined(JS_CODEGEN_X64)
     // Global data accesses on x64 use rip-relative addressing and thus do
     // not need patching after deserialization.
     for (size_t i = 0; i < masm.numAsmJSGlobalAccesses(); i++) {
         AsmJSGlobalAccess a = masm.asmJSGlobalAccess(i);
         masm.patchAsmJSGlobalAccess(a.patchAt, code_, globalData(), a.globalDataOffset);
@@ -821,17 +832,17 @@ AsmJSModule::initHeap(Handle<ArrayBuffer
     // CodeGeneratorX64::visitAsmJS{Load,Store,CompareExchange,Exchange,AtomicBinop}Heap)
     uint32_t heapLength = heap->byteLength();
     for (size_t i = 0; i < heapAccesses_.length(); i++) {
         const jit::AsmJSHeapAccess& access = heapAccesses_[i];
         // See comment above for x86 codegen.
         if (access.hasLengthCheck())
             X86Encoding::AddInt32(access.patchLengthAt(code_), heapLength);
     }
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     uint32_t heapLength = heap->byteLength();
     for (unsigned i = 0; i < heapAccesses_.length(); i++) {
         jit::Assembler::UpdateBoundsCheck(heapLength,
                                           (jit::Instruction*)(heapAccesses_[i].insnOffset() + code_));
     }
 #endif
 }
 
@@ -1734,16 +1745,19 @@ AsmJSModule::setProfilingEnabled(bool en
         void* callee = calleeOffset.getDest(callerInsn);
 #elif defined(JS_CODEGEN_ARM64)
         MOZ_CRASH();
         void* callee = nullptr;
         (void)callerRetAddr;
 #elif defined(JS_CODEGEN_MIPS32)
         Instruction* instr = (Instruction*)(callerRetAddr - 4 * sizeof(uint32_t));
         void* callee = (void*)Assembler::ExtractLuiOriValue(instr, instr->next());
+#elif defined(JS_CODEGEN_MIPS64)
+        Instruction* instr = (Instruction*)(callerRetAddr - 6 * sizeof(uint32_t));
+        void* callee = (void*)Assembler::ExtractLoad64Value(instr);
 #elif defined(JS_CODEGEN_NONE)
         MOZ_CRASH();
         void* callee = nullptr;
 #else
 # error "Missing architecture"
 #endif
 
         const CodeRange* codeRange = lookupCodeRange(callee);
@@ -1762,16 +1776,19 @@ AsmJSModule::setProfilingEnabled(bool en
         new (caller) InstBLImm(BOffImm(newCallee - caller), Assembler::Always);
 #elif defined(JS_CODEGEN_ARM64)
         (void)newCallee;
         MOZ_CRASH();
 #elif defined(JS_CODEGEN_MIPS32)
         Assembler::WriteLuiOriInstructions(instr, instr->next(),
                                            ScratchRegister, (uint32_t)newCallee);
         instr[2] = InstReg(op_special, ScratchRegister, zero, ra, ff_jalr);
+#elif defined(JS_CODEGEN_MIPS64)
+        Assembler::WriteLoad64Instructions(instr, ScratchRegister, (uint64_t)newCallee);
+        instr[4] = InstReg(op_special, ScratchRegister, zero, ra, ff_jalr);
 #elif defined(JS_CODEGEN_NONE)
         MOZ_CRASH();
 #else
 # error "Missing architecture"
 #endif
     }
 
     // Update all the addresses in the function-pointer tables to point to the
@@ -1836,16 +1853,28 @@ AsmJSModule::setProfilingEnabled(bool en
             Assembler::WriteLuiOriInstructions(instr, instr->next(),
                                                ScratchRegister, (uint32_t)profilingEpilogue);
             instr[2] = InstReg(op_special, ScratchRegister, zero, zero, ff_jr);
         } else {
             instr[0].makeNop();
             instr[1].makeNop();
             instr[2].makeNop();
         }
+#elif defined(JS_CODEGEN_MIPS64)
+        Instruction* instr = (Instruction*)jump;
+        if (enabled) {
+            Assembler::WriteLoad64Instructions(instr, ScratchRegister, (uint64_t)profilingEpilogue);
+            instr[4] = InstReg(op_special, ScratchRegister, zero, zero, ff_jr);
+        } else {
+            instr[0].makeNop();
+            instr[1].makeNop();
+            instr[2].makeNop();
+            instr[3].makeNop();
+            instr[4].makeNop();
+        }
 #elif defined(JS_CODEGEN_NONE)
         MOZ_CRASH();
 #else
 # error "Missing architecture"
 #endif
     }
 
     // Replace all calls to builtins with calls to profiling thunks that push a
@@ -1876,16 +1905,17 @@ AsmJSModule::setProfilingEnabled(bool en
 static bool
 GetCPUID(uint32_t* cpuId)
 {
     enum Arch {
         X86 = 0x1,
         X64 = 0x2,
         ARM = 0x3,
         MIPS = 0x4,
+        MIPS64 = 0x5,
         ARCH_BITS = 3
     };
 
 #if defined(JS_CODEGEN_X86)
     MOZ_ASSERT(uint32_t(CPUInfo::GetSSEVersion()) <= (UINT32_MAX >> ARCH_BITS));
     *cpuId = X86 | (uint32_t(CPUInfo::GetSSEVersion()) << ARCH_BITS);
     return true;
 #elif defined(JS_CODEGEN_X64)
@@ -1895,16 +1925,20 @@ GetCPUID(uint32_t* cpuId)
 #elif defined(JS_CODEGEN_ARM)
     MOZ_ASSERT(GetARMFlags() <= (UINT32_MAX >> ARCH_BITS));
     *cpuId = ARM | (GetARMFlags() << ARCH_BITS);
     return true;
 #elif defined(JS_CODEGEN_MIPS32)
     MOZ_ASSERT(GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
     *cpuId = MIPS | (GetMIPSFlags() << ARCH_BITS);
     return true;
+#elif defined(JS_CODEGEN_MIPS64)
+    MOZ_ASSERT(GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
+    *cpuId = MIPS64 | (GetMIPSFlags() << ARCH_BITS);
+    return true;
 #else
     return false;
 #endif
 }
 
 class MachineId
 {
     uint32_t cpuId_;
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -741,35 +741,35 @@ class AsmJSModule
             InstructionImmediate
         };
 
         RelativeLink()
         { }
 
         explicit RelativeLink(Kind kind)
         {
-#if defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
             kind_ = kind;
 #elif defined(JS_CODEGEN_ARM)
             // On ARM, CodeLabels are only used to label raw pointers, so in
             // all cases on ARM, a RelativePatch means patching a raw pointer.
             MOZ_ASSERT(kind == CodeLabel || kind == RawPointer);
 #endif
             // On X64 and X86, all RelativePatch-es are patched as raw pointers.
         }
 
         bool isRawPointerPatch() {
-#if defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
             return kind_ == RawPointer;
 #else
             return true;
 #endif
         }
 
-#ifdef JS_CODEGEN_MIPS32
+#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
         Kind kind_;
 #endif
         uint32_t patchAtOffset;
         uint32_t targetOffset;
     };
 
     typedef Vector<RelativeLink, 0, SystemAllocPolicy> RelativeLinkVector;
 
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -7050,37 +7050,37 @@ GenerateEntry(ModuleValidator& m, unsign
 
     Label begin;
     masm.haltingAlign(CodeAlignment);
     masm.bind(&begin);
 
     // Save the return address if it wasn't already saved by the call insn.
 #if defined(JS_CODEGEN_ARM)
     masm.push(lr);
-#elif defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     masm.push(ra);
 #elif defined(JS_CODEGEN_X86)
     static const unsigned EntryFrameSize = sizeof(void*);
 #endif
 
     // Save all caller non-volatile registers before we clobber them here and in
     // the asm.js callee (which does not preserve non-volatile registers).
     masm.setFramePushed(0);
     masm.PushRegsInMask(NonVolatileRegs);
     MOZ_ASSERT(masm.framePushed() == FramePushedAfterSave);
 
-    // ARM and MIPS have a globally-pinned GlobalReg (x64 uses RIP-relative
+    // ARM and MIPS/MIPS64 have a globally-pinned GlobalReg (x64 uses RIP-relative
     // addressing, x86 uses immediates in effective addresses). For the
     // AsmJSGlobalRegBias addition, see Assembler-(mips,arm).h.
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     masm.movePtr(IntArgReg1, GlobalReg);
     masm.addPtr(Imm32(AsmJSGlobalRegBias), GlobalReg);
 #endif
 
-    // ARM, MIPS and x64 have a globally-pinned HeapReg (x86 uses immediates in
+    // ARM, MIPS/MIPS64 and x64 have a globally-pinned HeapReg (x86 uses immediates in
     // effective addresses). Loading the heap register depends on the global
     // register already having been loaded.
     masm.loadAsmJSHeapRegisterFromGlobalData();
 
     // Put the 'argv' argument into a non-argument/return register so that we
     // can use 'argv' while we fill in the arguments for the asm.js callee.
     // Also, save 'argv' on the stack so that we can recover it after the call.
     // Use a second non-argument/return register as temporary scratch.
@@ -7379,17 +7379,17 @@ GenerateFFIInterpExit(ModuleValidator& m
     masm.loadAsmJSHeapRegisterFromGlobalData();
     GenerateCheckForHeapDetachment(m, ABIArgGenerator::NonReturn_VolatileReg0);
 
     Label profilingReturn;
     GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::SlowFFI, &profilingReturn);
     return !masm.oom() && m.finishGeneratingInterpExit(exitIndex, &begin, &profilingReturn);
 }
 
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
 static const unsigned MaybeSavedGlobalReg = sizeof(void*);
 #else
 static const unsigned MaybeSavedGlobalReg = 0;
 #endif
 
 static bool
 GenerateFFIIonExit(ModuleValidator& m, const Signature& sig, unsigned exitIndex,
                    Label* throwLabel)
@@ -7423,17 +7423,17 @@ GenerateFFIIonExit(ModuleValidator& m, c
     Register scratch = ABIArgGenerator::NonArgReturnReg1;  // repeatedly clobbered
 
     // 2.1. Get ExitDatum
     unsigned globalDataOffset = m.module().exitIndexToGlobalDataOffset(exitIndex);
 #if defined(JS_CODEGEN_X64)
     m.masm().append(AsmJSGlobalAccess(masm.leaRipRelative(callee), globalDataOffset));
 #elif defined(JS_CODEGEN_X86)
     m.masm().append(AsmJSGlobalAccess(masm.movlWithPatch(Imm32(0), callee), globalDataOffset));
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     masm.computeEffectiveAddress(Address(GlobalReg, globalDataOffset - AsmJSGlobalRegBias), callee);
 #endif
 
     // 2.2. Get callee
     masm.loadPtr(Address(callee, offsetof(AsmJSModule::ExitDatum, fun)), callee);
 
     // 2.3. Save callee
     masm.storePtr(callee, Address(masm.getStackPointer(), argOffset));
@@ -7459,17 +7459,17 @@ GenerateFFIIonExit(ModuleValidator& m, c
     MOZ_ASSERT(argOffset == ionFrameBytes);
 
     // 6. Jit code will clobber all registers, even non-volatiles. GlobalReg and
     //    HeapReg are removed from the general register set for asm.js code, so
     //    these will not have been saved by the caller like all other registers,
     //    so they must be explicitly preserved. Only save GlobalReg since
     //    HeapReg must be reloaded (from global data) after the call since the
     //    heap may change during the FFI call.
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     static_assert(MaybeSavedGlobalReg == sizeof(void*), "stack frame accounting");
     masm.storePtr(GlobalReg, Address(masm.getStackPointer(), ionFrameBytes));
 #endif
 
     {
         // Enable Activation.
         //
         // This sequence requires four registers, and needs to preserve the 'callee'
@@ -7575,17 +7575,17 @@ GenerateFFIIonExit(ModuleValidator& m, c
         masm.storePtr(reg2, Address(reg0, offsetOfJitJSContext));
 
         //   rt->jitActivation = prevJitActivation_;
         masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitActivation()), reg2);
         masm.storePtr(reg2, Address(reg0, offsetOfJitActivation));
     }
 
     // Reload the global register since Ion code can clobber any register.
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32)
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     static_assert(MaybeSavedGlobalReg == sizeof(void*), "stack frame accounting");
     masm.loadPtr(Address(masm.getStackPointer(), ionFrameBytes), GlobalReg);
 #endif
 
     // As explained above, the frame was aligned for Ion such that
     //   (sp + sizeof(void*)) % JitStackAlignment == 0
     // But now we possibly want to call one of several different C++ functions,
     // so subtract the sizeof(void*) so that sp is aligned for an ABI call.
@@ -7890,17 +7890,17 @@ GenerateAsyncInterruptExit(ModuleValidat
 
     // Restore the StackPointer to its position before the call.
     masm.moveToStackPtr(ABIArgGenerator::NonVolatileReg);
 
     // Restore the machine state to before the interrupt.
     masm.PopRegsInMask(AllRegsExceptSP); // restore all GP/FP registers (except SP)
     masm.popFlags();              // after this, nothing that sets conditions
     masm.ret();                   // pop resumePC into PC
-#elif defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     // Reserve space to store resumePC.
     masm.subFromStackPtr(Imm32(sizeof(intptr_t)));
     // set to zero so we can use masm.framePushed() below.
     masm.setFramePushed(0);
     // When this platform supports SIMD extensions, we'll need to push high lanes
     // of SIMD registers as well.
     JS_STATIC_ASSERT(!SupportsSimd);
     // save all registers,except sp. After this stack is alligned.
--- a/js/src/ds/LifoAlloc.h
+++ b/js/src/ds/LifoAlloc.h
@@ -271,24 +271,36 @@ class LifoAlloc
 
     MOZ_ALWAYS_INLINE
     void* alloc(size_t n) {
         JS_OOM_POSSIBLY_FAIL();
         return allocImpl(n);
     }
 
     MOZ_ALWAYS_INLINE
-    void* allocInfallible(size_t n) {
+    void* allocInfallibleOrAssert(size_t n) {
+        void* result = allocImpl(n);
+        MOZ_RELEASE_ASSERT(result, "[OOM] Is it really infallible?");
+        return result;
+    }
+
+    MOZ_ALWAYS_INLINE
+    void* allocInfallibleOrCrash(size_t n) {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (void* result = allocImpl(n))
             return result;
         oomUnsafe.crash("LifoAlloc::allocInfallible");
         return nullptr;
     }
 
+    MOZ_ALWAYS_INLINE
+    void* allocInfallible(size_t n) {
+        return allocInfallibleOrCrash(n);
+    }
+
     // Ensures that enough space exists to satisfy N bytes worth of
     // allocation requests, not necessarily contiguous. Note that this does
     // not guarantee a successful single allocation of N bytes.
     MOZ_ALWAYS_INLINE
     bool ensureUnusedApproximate(size_t n) {
         size_t total = 0;
         for (BumpChunk* chunk = latest; chunk; chunk = chunk->next()) {
             total += chunk->unused();
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -5190,16 +5190,20 @@ Parser<FullParseHandler>::forStatement(Y
 
     MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
 
     /*
      * True if we have 'for (var/let/const ...)'.
      */
     bool isForDecl = false;
 
+    // True if a 'let' token at the head is parsed as an identifier instead of
+    // as starting a declaration.
+    bool letIsIdentifier = false;
+
     /* Non-null when isForDecl is true for a 'for (let ...)' statement. */
     RootedStaticBlockObject blockObj(context);
 
     /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */
     ParseNode* pn1;
 
     TokenStream::Modifier modifier = TokenStream::Operand;
     {
@@ -5217,29 +5221,48 @@ Parser<FullParseHandler>::forStatement(Y
             // expression.  But for declarations at the start of a for-loop
             // head, initializers can't contain |in|.  (Such syntax conflicts
             // with ES5's |for (var i = 0 in foo)| syntax, removed in ES6, that
             // we "support" by ignoring the |= 0|.)
             if (tt == TOK_VAR) {
                 isForDecl = true;
                 tokenStream.consumeKnownToken(tt, TokenStream::Operand);
                 pn1 = variables(yieldHandling, PNK_VAR, InForInit);
-            } else if (tt == TOK_LET || tt == TOK_CONST) {
+            } else if (tt == TOK_LET || tt == TOK_CONST ||
+                       (tt == TOK_NAME && tokenStream.nextName() == context->names().let)) {
                 handler.disableSyntaxParser();
-                bool constDecl = tt == TOK_CONST;
-                tokenStream.consumeKnownToken(tt, TokenStream::Operand);
-                isForDecl = true;
-                blockObj = StaticBlockObject::create(context);
-                if (!blockObj)
-                    return null();
-                // Initialize the enclosing scope manually for the call to
-                // |variables| below.
-                blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
-                pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit,
-                                nullptr, blockObj, DontHoistVars);
+
+                // Check for the backwards-compatibility corner case in sloppy
+                // mode like |for (let in e)| where the 'let' token should be
+                // parsed as an identifier.
+                bool parseDecl;
+                if (tt == TOK_NAME) {
+                    if (!peekShouldParseLetDeclaration(&parseDecl, TokenStream::Operand))
+                        return null();
+                    letIsIdentifier = !parseDecl;
+                } else {
+                    parseDecl = true;
+                    tokenStream.consumeKnownToken(tt, TokenStream::Operand);
+                }
+
+                if (parseDecl) {
+                    bool constDecl = tt == TOK_CONST;
+                    isForDecl = true;
+                    blockObj = StaticBlockObject::create(context);
+                    if (!blockObj)
+                        return null();
+
+                    // Initialize the enclosing scope manually for the call to
+                    // |variables| below.
+                    blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope());
+                    pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit,
+                                    nullptr, blockObj, DontHoistVars);
+                } else {
+                    pn1 = expr(InProhibited, yieldHandling, TripledotProhibited);
+                }
             } else {
                 // Pass |InProhibited| when parsing an expression so that |in|
                 // isn't parsed in a RelationalExpression as a binary operator.
                 // In this context, |in| is part of a for-in loop -- *not* part
                 // of a binary expression.
                 pn1 = expr(InProhibited, yieldHandling, TripledotProhibited);
             }
             if (!pn1)
@@ -5304,19 +5327,27 @@ Parser<FullParseHandler>::forStatement(Y
     Maybe<AutoPushStmtInfoPC> letStmt; /* used if blockObj != nullptr. */
     ParseNode* pn2;      /* forHead->pn_kid2 */
     ParseNode* pn3;      /* forHead->pn_kid3 */
     ParseNodeKind headKind = PNK_FORHEAD;
     if (pn1) {
         bool isForIn, isForOf;
         if (!matchInOrOf(&isForIn, &isForOf))
             return null();
+
+        // In for-in loops, a 'let' token may be used as an identifier for
+        // backwards-compatibility reasons, e.g., |for (let in e)|. In for-of
+        // loops, a 'let' token is never parsed as an identifier. Forbid
+        // trying to parse a for-of loop if we have parsed a 'let' token as an
+        // identifier above.
+        //
+        // See ES6 13.7.5.1.
         if (isForIn)
             headKind = PNK_FORIN;
-        else if (isForOf)
+        else if (isForOf && !letIsIdentifier)
             headKind = PNK_FOROF;
     }
 
     if (headKind == PNK_FOROF || headKind == PNK_FORIN) {
         /*
          * Parse the rest of the for/in or for/of head.
          *
          * Here pn1 is everything to the left of 'in' or 'of'. At the end of
@@ -5562,22 +5593,22 @@ Parser<SyntaxParseHandler>::forStatement
         if (tt == TOK_SEMI) {
             lhsNode = null();
         } else {
             /* Set lhsNode to a var list or an initializing expression. */
             if (tt == TOK_VAR) {
                 isForDecl = true;
                 tokenStream.consumeKnownToken(tt, TokenStream::Operand);
                 lhsNode = variables(yieldHandling, PNK_VAR, InForInit, &simpleForDecl);
-            }
-            else if (tt == TOK_CONST || tt == TOK_LET) {
+            } else if (tt == TOK_CONST || tt == TOK_LET ||
+                       (tt == TOK_NAME && tokenStream.nextName() == context->names().let))
+            {
                 JS_ALWAYS_FALSE(abortIfSyntaxParser());
                 return null();
-            }
-            else {
+            } else {
                 lhsNode = expr(InProhibited, yieldHandling, TripledotProhibited);
             }
             if (!lhsNode)
                 return null();
             modifier = TokenStream::None;
         }
     }
 
@@ -6623,16 +6654,81 @@ Parser<SyntaxParseHandler>::classDefinit
                                             ClassContext classContext,
                                             DefaultHandling defaultHandling)
 {
     MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
     return SyntaxParseHandler::NodeFailure;
 }
 
 template <typename ParseHandler>
+bool
+Parser<ParseHandler>::shouldParseLetDeclaration(bool* parseDeclOut)
+{
+    // 'let' is a reserved keyword in strict mode and we shouldn't get here.
+    MOZ_ASSERT(!pc->sc->strict());
+
+    TokenKind tt;
+    *parseDeclOut = false;
+
+    if (!tokenStream.peekToken(&tt))
+        return false;
+
+    switch (tt) {
+      case TOK_NAME:
+        // |let let| is disallowed per ES6 13.3.1.1.
+        *parseDeclOut = tokenStream.nextName() != context->names().let;
+        break;
+
+      case TOK_LC:
+      case TOK_LB:
+        // A following name is always a declaration.
+        //
+        // |let {| and |let [| are destructuring declarations.
+        *parseDeclOut = true;
+        break;
+
+      case TOK_LP:
+        // Only parse let blocks for 1.7 and 1.8. Do not expose deprecated let
+        // blocks to content.
+        *parseDeclOut = versionNumber() == JSVERSION_1_7 || versionNumber() == JSVERSION_1_8;
+        break;
+
+      default:
+        break;
+    }
+
+    return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::peekShouldParseLetDeclaration(bool* parseDeclOut,
+                                                    TokenStream::Modifier modifier)
+{
+    *parseDeclOut = false;
+
+#ifdef DEBUG
+    TokenKind tt;
+    if (!tokenStream.peekToken(&tt, modifier))
+        return false;
+    MOZ_ASSERT(tt == TOK_NAME && tokenStream.nextName() == context->names().let);
+#endif
+
+    tokenStream.consumeKnownToken(TOK_NAME, modifier);
+    if (!shouldParseLetDeclaration(parseDeclOut))
+        return false;
+
+    // Unget the TOK_NAME of 'let' if not parsing a declaration.
+    if (!*parseDeclOut)
+        tokenStream.ungetToken();
+
+    return true;
+}
+
+template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::statement(YieldHandling yieldHandling, bool canHaveDirectives)
 {
     MOZ_ASSERT(checkOptionsCalled);
 
     JS_CHECK_RECURSION(context, return null());
 
     TokenKind tt;
@@ -6691,16 +6787,26 @@ Parser<ParseHandler>::statement(YieldHan
             if (!checkYieldNameValidity())
                 return null();
             return labeledStatement(yieldHandling);
         }
         return expressionStatement(yieldHandling);
       }
 
       case TOK_NAME: {
+        // 'let' is a contextual keyword in sloppy node. In strict mode, it is
+        // always lexed as TOK_LET.
+        if (tokenStream.currentName() == context->names().let) {
+            bool parseDecl;
+            if (!shouldParseLetDeclaration(&parseDecl))
+                return null();
+            if (parseDecl)
+                return lexicalDeclaration(yieldHandling, /* isConst = */ false);
+        }
+
         TokenKind next;
         if (!tokenStream.peekToken(&next))
             return null();
         if (next == TOK_COLON)
             return labeledStatement(yieldHandling);
         return expressionStatement(yieldHandling);
       }
 
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -795,16 +795,25 @@ class Parser : private JS::AutoGCRooter,
                                  bool* pbodyProcessed);
     bool finishFunctionDefinition(Node pn, FunctionBox* funbox, Node body);
     bool addFreeVariablesFromLazyFunction(JSFunction* fun, ParseContext<ParseHandler>* pc);
 
     bool isValidForStatementLHS(Node pn1, JSVersion version, bool forDecl, bool forEach,
                                 ParseNodeKind headKind);
     bool checkForHeadConstInitializers(Node pn1);
 
+    // Use when the current token is TOK_NAME and is known to be 'let'.
+    bool shouldParseLetDeclaration(bool* parseDeclOut);
+
+    // Use when the lookahead toke