Merge mozilla-central to inbound. r=merge a=merge on a CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Wed, 08 Nov 2017 12:57:37 +0200
changeset 443983 60fd4a5b01ec70ded9ddfd560fd5be191b1c74b9
parent 443982 2af1c7c2375f251cd8dd53b05d05087378ab709d (current diff)
parent 443974 f63559d7e6a570e4e73ba367964099394248e18d (diff)
child 443984 49451ebb746c027e54388d572e40be5489233d60
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. r=merge a=merge on a CLOSED TREE
build/unix/elfhack/inject/Makefile.in
ipc/chromium/src/base/process_posix.cc
ipc/chromium/src/base/process_win.cc
mobile/android/tests/background/junit3/AndroidManifest.xml.in
mobile/android/tests/background/junit3/Makefile.in
mobile/android/tests/background/junit3/background_junit3_sources.mozbuild
mobile/android/tests/background/junit3/instrumentation.ini
mobile/android/tests/background/junit3/moz.build
mobile/android/tests/background/junit3/res/drawable-hdpi/icon.png
mobile/android/tests/background/junit3/res/drawable-ldpi/icon.png
mobile/android/tests/background/junit3/res/drawable-mdpi/icon.png
mobile/android/tests/background/junit3/res/layout/main.xml
mobile/android/tests/background/junit3/res/values/strings.xml
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/common/TestAndroidLogWriters.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/common/TestUtils.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/common/TestWaitHelper.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestAndroidBrowserBookmarksRepository.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestAndroidBrowserHistoryRepository.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestBookmarks.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestClientsDatabase.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestClientsDatabaseAccessor.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestPasswordsRepository.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/TestTopSites.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/db/ThreadedRepositoryTestCase.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/fxa/TestAccountLoader.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/fxa/TestBrowserIDKeyPairGeneration.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/fxa/authenticator/TestAccountPickler.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/helpers/AndroidSyncTestCase.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/helpers/DBHelpers.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/helpers/DBProviderTestCase.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/nativecode/test/TestNativeCrypto.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/AndroidSyncTestCaseWithAccounts.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/TestClientsStage.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/TestResetting.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/TestStoreTracking.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/TestSyncConfiguration.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/TestWebURLFinder.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/BookmarkHelpers.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultBeginDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultCleanDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultFetchDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultFinishDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultSessionCreationDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/DefaultStoreDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectBeginDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectBeginFailDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectFetchDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectFetchSinceDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectFinishDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectFinishFailDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectInvalidRequestFetchDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectInvalidTypeStoreDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectManyStoredDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectNoStoreDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectStoreCompletedDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/ExpectStoredDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/HistoryHelpers.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/PasswordHelpers.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/SessionTestHelper.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessBeginDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessCreationDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessFetchDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessFinishDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessStoreDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/BaseMockServerSyncStage.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/CommandHelpers.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/DefaultGlobalSessionCallback.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockAbstractNonRepositorySyncStage.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockClientsDataDelegate.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockClientsDatabaseAccessor.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockRecord.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockServerSyncStage.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/MockSharedPreferences.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/WBORepository.java
mobile/android/tests/background/junit3/src/org/mozilla/gecko/background/testhelpers/WaitHelper.java
mobile/android/tests/background/junit4/resources/dlc_sync_deleted_item.json
mobile/android/tests/background/junit4/resources/dlc_sync_old_format.json
mobile/android/tests/background/junit4/resources/dlc_sync_single_font.json
mobile/android/tests/background/junit4/resources/experiments.json
mobile/android/tests/background/junit4/resources/feed_atom_blogger.xml
mobile/android/tests/background/junit4/resources/feed_atom_feedburner.xml
mobile/android/tests/background/junit4/resources/feed_atom_planetmozilla.xml
mobile/android/tests/background/junit4/resources/feed_atom_wikipedia.xml
mobile/android/tests/background/junit4/resources/feed_rss10_planetmozilla.xml
mobile/android/tests/background/junit4/resources/feed_rss20_planetmozilla.xml
mobile/android/tests/background/junit4/resources/feed_rss_heise.xml
mobile/android/tests/background/junit4/resources/feed_rss_medium.xml
mobile/android/tests/background/junit4/resources/feed_rss_spon.xml
mobile/android/tests/background/junit4/resources/feed_rss_tumblr.xml
mobile/android/tests/background/junit4/resources/feed_rss_wikipedia.xml
mobile/android/tests/background/junit4/resources/feed_rss_wordpress.xml
mobile/android/tests/background/junit4/resources/robolectric.properties
mobile/android/tests/background/junit4/src/com/keepsafe/switchboard/TestSwitchboard.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestBackoff.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestBrowserIDAuthHeaderProvider.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestCredentialsEndToEnd.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestGlobalSession.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestHeaderParsing.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestLineByLineHandling.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestMetaGlobal.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestResource.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestRetryAfter.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestServer15Repository.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestSyncStorageRequest.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/SynchronizerHelpers.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCollectionKeys.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCommandProcessor.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestCryptoRecord.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestRecord.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestRecordsChannel.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestResetCommands.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestServer15RepositorySession.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestServerLocalSynchronizer.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSynchronizer.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestSynchronizerSession.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/TestUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/BaseTestStorageRequestDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionBeginDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionCreationDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionFetchRecordsDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionFinishDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionStoreDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositoryWipeDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/HTTPServerTestHelper.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockGlobalSessionCallback.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockResourceDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockServer.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockSyncClientsEngineStage.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/MockWBOServer.java
mobile/android/tests/background/junit4/src/org/mozilla/android/sync/test/helpers/test/TestHTTPServerTestHelper.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/GeckoNetworkManagerTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/GlobalPageMetadataTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/TestGeckoProfile.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/activitystream/homepanel/TestActivityStreamConfiguration.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/activitystream/homepanel/topstories/TestPocketStoriesLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/activitystream/ranking/TestHighlightsRanking.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/activitystream/ranking/TestRankingUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/common/log/writers/test/TestLogWriters.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/db/TestTabsProvider.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountClient20.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/fxa/test/TestFxAccountUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/test/EntityTestHelper.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/BaseMockServerSyncStage.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/CommandHelpers.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/DefaultGlobalSessionCallback.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockAbstractNonRepositorySyncStage.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockClientsDataDelegate.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockClientsDatabaseAccessor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockRecord.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockServerSyncStage.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/MockSharedPreferences.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/TestRunner.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/WBORepository.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/WaitHelper.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestASNUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestDSACryptoImplementation.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestJSONWebTokenUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/browserid/test/TestRSACryptoImplementation.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/cleanup/TestFileCleanupController.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/cleanup/TestFileCleanupService.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestCustomTabsActivity.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/customtabs/TestIntentUtil.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserContractTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserDatabaseHelperTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderVisitsTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/LocalBrowserDBTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/distribution/TestReferrerDescriptor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestCleanupAction.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestStudyAction.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestSyncAction.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestVerifyAction.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/catalog/TestDownloadContentBuilder.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/catalog/TestDownloadContentCatalog.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/TestSkewHandler.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/authenticator/AndroidFxAccountTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceRegistrator.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/TestFxAccountLoginStateMachine.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/TestStateFactory.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/helpers/AssertUtil.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/helpers/MockUserManager.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/home/TestHomeConfigPrefsBackendMigration.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconDescriptor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconDescriptorComparator.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconRequest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconRequestBuilder.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconResponse.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconTask.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/TestIconsHelper.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestContentProviderLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestDataUriLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestDiskLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconDownloader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconGenerator.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestJarLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestMemoryLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestSuggestedSiteLoader.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestAboutPagesPreparer.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestAddDefaultIconUrl.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestFilterKnownFailureUrls.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestFilterMimeTypes.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestFilterPrivilegedUrls.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestLookupIconUrl.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/preparation/TestSuggestedSitePreparer.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestDiskProcessor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestMemoryProcessor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestMinimumSizeProcessor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestResizingProcessor.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/media/TestMediaControlService.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/permissions/TestPermissions.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/push/TestPushManager.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/push/TestPushState.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/push/autopush/test/TestAutopushClient.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/push/autopush/test/TestLiveAutopushClient.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestBase32.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestCryptoInfo.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestHKDF.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestKeyBundle.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestPBKDF2.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestPersistedCrypto5Keys.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/crypto/test/TestSRPConstants.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepositorySessionTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/middleware/test/TestCrypto5MiddlewareRepositorySession.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestHMACAuthHeaderProvider.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestHawkAuthHeaderProvider.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestLiveHawkAuth.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/net/test/TestUserAgentHeaders.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/BookmarksSessionHelperTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpersTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/HistorySessionHelperTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/android/test/TestBookmarksInsertionManager.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/TestClientRecord.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/test/TestFormHistoryRecord.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/domain/test/TestPasswordRecord.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderControllerTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegateTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/test/TestRepositorySessionBundle.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/BatchMetaTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploaderTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/PayloadTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegateTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnableTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/repositories/uploaders/UploaderMetaTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestEnsureCrypto5KeysStage.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestFetchMetaGlobalStage.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/stage/test/TestStageLookup.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/telemetry/TelemetryCollectorTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestBookmarkValidator.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestExtendedJSONObject.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestInfoCollections.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/sync/test/TestPersistedMetaGlobal.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/measurements/TestSearchCountMeasurements.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/measurements/TestSessionMeasurements.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/pingbuilders/TelemetrySyncPingBuilderTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/pingbuilders/TelemetrySyncPingBundleBuilderTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/pingbuilders/TestTelemetryPingBuilder.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/schedulers/TestTelemetryUploadAllPingsImmediatelyScheduler.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/telemetry/stores/TestTelemetryJSONFilePingStore.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/tokenserver/test/TestTokenServerClient.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/toolbar/TestSecurityModeUtil.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/NetworkUtilsTest.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestContextUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestDateUtil.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestFileUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestFloatUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestIntentUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestStringUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestURIUtils.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/TestUUIDUtil.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/util/publicsuffix/TestPublicSuffix.java
mobile/android/tests/background/moz.build
mobile/android/tests/browser/junit3/AndroidManifest.xml.in
mobile/android/tests/browser/junit3/Makefile.in
mobile/android/tests/browser/junit3/instrumentation.ini
mobile/android/tests/browser/junit3/moz.build
mobile/android/tests/browser/junit3/res/drawable-hdpi/icon.png
mobile/android/tests/browser/junit3/res/drawable-ldpi/icon.png
mobile/android/tests/browser/junit3/res/drawable-mdpi/icon.png
mobile/android/tests/browser/junit3/res/layout/main.xml
mobile/android/tests/browser/junit3/res/values/strings.xml
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestDistribution.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoBackgroundThread.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoMenu.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoProfilesProvider.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestGeckoSharedPrefs.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestImageDownloader.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestJarReader.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestRawResource.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserInstrumentationTestRunner.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/harness/BrowserTestListener.java
testing/instrumentation/Makefile.in
testing/instrumentation/moz.build
testing/instrumentation/runinstrumentation.py
toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
--- a/Makefile.in
+++ b/Makefile.in
@@ -217,16 +217,41 @@ ifdef ENABLE_TESTS
 # Additional makefile targets to call automated test suites
 include $(topsrcdir)/testing/testsuite-targets.mk
 endif
 endif
 
 default all::
 	$(call BUILDSTATUS,TIERS $(TIERS) $(if $(MOZ_AUTOMATION),$(MOZ_AUTOMATION_TIERS)))
 
+# PGO build target.
+profiledbuild::
+	$(call BUILDSTATUS,TIERS pgo_profile_generate pgo_package pgo_profile pgo_clobber pgo_profile_use)
+	$(call BUILDSTATUS,TIER_START pgo_profile_generate)
+	$(MAKE) default MOZ_PROFILE_GENERATE=1 MOZ_PGO_INSTRUMENTED=1
+	$(call BUILDSTATUS,TIER_FINISH pgo_profile_generate)
+	$(call BUILDSTATUS,TIER_START pgo_package)
+	$(MAKE) package MOZ_PGO_INSTRUMENTED=1 MOZ_INTERNAL_SIGNING_FORMAT= MOZ_EXTERNAL_SIGNING_FORMAT=
+	rm -f jarlog/en-US.log
+	$(call BUILDSTATUS,TIER_FINISH pgo_package)
+	$(call BUILDSTATUS,TIER_START pgo_profile)
+	MOZ_PGO_INSTRUMENTED=1 JARLOG_FILE=jarlog/en-US.log $(PYTHON) $(topsrcdir)/build/pgo/profileserver.py 10
+	$(call BUILDSTATUS,TIER_FINISH pgo_profile)
+	$(call BUILDSTATUS,TIER_START pgo_clobber)
+	$(MAKE) maybe_clobber_profiledbuild
+	$(call BUILDSTATUS,TIER_FINISH pgo_clobber)
+	$(call BUILDSTATUS,TIER_START pgo_profile_use)
+	$(MAKE) default MOZ_PROFILE_USE=1
+	$(call BUILDSTATUS,TIER_FINISH pgo_profile_use)
+
+# Change default target to PGO build if PGO is enabled.
+ifdef MOZ_PGO
+OVERRIDE_DEFAULT_GOAL := profiledbuild
+endif
+
 include $(topsrcdir)/config/rules.mk
 
 ifdef SCCACHE_VERBOSE_STATS
 default::
 	-$(CCACHE) --show-stats --stats-format=json > sccache-stats.json
 	@echo "===SCCACHE STATS==="
 	-$(CCACHE) --show-stats
 	@echo "==================="
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -80,16 +80,17 @@ const SEC_ERROR_EXPIRED_CERTIFICATE     
 const SEC_ERROR_UNKNOWN_ISSUER                     = SEC_ERROR_BASE + 13;
 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE         = SEC_ERROR_BASE + 30;
 const SEC_ERROR_OCSP_FUTURE_RESPONSE               = SEC_ERROR_BASE + 131;
 const SEC_ERROR_OCSP_OLD_RESPONSE                  = SEC_ERROR_BASE + 132;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
 
 const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
+const PREF_BLOCKLIST_LAST_FETCHED = "services.blocklist.last_update_seconds";
 
 const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
 
 
 function getSerializedSecurityInfo(docShell) {
   let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
                     .getService(Ci.nsISerializationHelper);
 
@@ -257,16 +258,34 @@ var AboutNetAndCertErrorListener = {
         this.onCertErrorDetails(msg);
         break;
       case "Browser:CaptivePortalFreed":
         this.onCaptivePortalFreed(msg);
         break;
     }
   },
 
+  _getCertValidityRange() {
+    let {securityInfo} = docShell.failedChannel;
+    securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+    let certs = securityInfo.failedCertChain.getEnumerator();
+    let notBefore = 0;
+    let notAfter = Number.MAX_SAFE_INTEGER;
+    while (certs.hasMoreElements()) {
+      let cert = certs.getNext();
+      cert.QueryInterface(Ci.nsIX509Cert);
+      notBefore = Math.max(notBefore, cert.validity.notBefore);
+      notAfter = Math.min(notAfter, cert.validity.notAfter);
+    }
+    // nsIX509Cert reports in PR_Date terms, which uses microseconds. Convert:
+    notBefore /= 1000;
+    notAfter /= 1000;
+    return {notBefore, notAfter};
+  },
+
   onCertErrorDetails(msg) {
     let div = content.document.getElementById("certificateErrorText");
     div.textContent = msg.data.info;
     let learnMoreLink = content.document.getElementById("learnMoreLink");
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
 
     switch (msg.data.code) {
       case SEC_ERROR_UNKNOWN_ISSUER:
@@ -279,36 +298,40 @@ var AboutNetAndCertErrorListener = {
       case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
       case SEC_ERROR_OCSP_FUTURE_RESPONSE:
       case SEC_ERROR_OCSP_OLD_RESPONSE:
       case MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
       case MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
 
         // We check against Kinto time first if available, because that allows us
         // to give the user an approximation of what the correct time is.
-        let difference = 0;
-        if (Services.prefs.getPrefType(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS)) {
-          difference = Services.prefs.getIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS);
-        }
+        let difference = Services.prefs.getIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, 0);
+        let lastFetched = Services.prefs.getIntPref(PREF_BLOCKLIST_LAST_FETCHED, 0) * 1000;
+
+        let now = Date.now();
+        let certRange = this._getCertValidityRange();
 
-        // If the difference is more than a day.
-        if (Math.abs(difference) > 60 * 60 * 24) {
+        let approximateDate = now - difference * 1000;
+        // If the difference is more than a day, we last fetched the date in the last 5 days,
+        // and adjusting the date per the interval would make the cert valid, warn the user:
+        if (Math.abs(difference) > 60 * 60 * 24 && (now - lastFetched) <= 60 * 60 * 24 * 5 &&
+            certRange.notBefore < approximateDate && certRange.notAfter > approximateDate) {
           let formatter = Services.intl.createDateTimeFormat(undefined, {
             dateStyle: "short"
           });
           let systemDate = formatter.format(new Date());
           // negative difference means local time is behind server time
-          let actualDate = formatter.format(new Date(Date.now() - difference * 1000));
+          approximateDate = formatter.format(new Date(approximateDate));
 
           content.document.getElementById("wrongSystemTime_URL")
             .textContent = content.document.location.hostname;
           content.document.getElementById("wrongSystemTime_systemDate")
             .textContent = systemDate;
           content.document.getElementById("wrongSystemTime_actualDate")
-            .textContent = actualDate;
+            .textContent = approximateDate;
 
           content.document.getElementById("errorShortDesc")
             .style.display = "none";
           content.document.getElementById("wrongSystemTimePanel")
             .style.display = "block";
 
         // If there is no clock skew with Kinto servers, check against the build date.
         // (The Kinto ping could have happened when the time was still right, or not at all)
@@ -317,17 +340,21 @@ var AboutNetAndCertErrorListener = {
 
           let year = parseInt(appBuildID.substr(0, 4), 10);
           let month = parseInt(appBuildID.substr(4, 2), 10) - 1;
           let day = parseInt(appBuildID.substr(6, 2), 10);
 
           let buildDate = new Date(year, month, day);
           let systemDate = new Date();
 
-          if (buildDate > systemDate) {
+          // We don't check the notBefore of the cert with the build date,
+          // as it is of course almost certain that it is now later than the build date,
+          // so we shouldn't exclude the possibility that the cert has become valid
+          // since the build date.
+          if (buildDate > systemDate && new Date(certRange.notAfter) > buildDate) {
             let formatter = Services.intl.createDateTimeFormat(undefined, {
               dateStyle: "short"
             });
 
             content.document.getElementById("wrongSystemTimeWithoutReference_URL")
               .textContent = content.document.location.hostname;
             content.document.getElementById("wrongSystemTimeWithoutReference_systemDate")
               .textContent = formatter.format(systemDate);
--- a/browser/base/content/test/about/browser_aboutCertError.js
+++ b/browser/base/content/test/about/browser_aboutCertError.js
@@ -212,17 +212,17 @@ add_task(async function checkWrongSystem
   await SpecialPowers.pushPrefEnv({set: [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]});
 
   info("Loading a bad cert page with no skewed clock");
   message = await setUpPage();
 
   is(message.divDisplay, "none", "Wrong time message information is not visible");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
+}).skip(); // Skipping because of bug 1414804.
 
 add_task(async function checkAdvancedDetails() {
   info("Loading a bad cert page and verifying the main error and advanced details section");
   let browser;
   let certErrorLoaded;
   await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
     gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, BAD_CERT);
     browser = gBrowser.selectedBrowser;
--- a/browser/config/mozconfigs/linux32/beta
+++ b/browser/config/mozconfigs/linux32/beta
@@ -2,12 +2,11 @@ if [ -n "$ENABLE_RELEASE_PROMOTION" ]; t
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
-
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux32/devedition
+++ b/browser/config/mozconfigs/linux32/devedition
@@ -12,13 +12,13 @@ ac_add_options --enable-verify-mar
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
 
 ac_add_options --with-branding=browser/branding/aurora
 
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 # Enable MOZ_ALLOW_LEGACY_EXTENSIONS
 ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux32/release
+++ b/browser/config/mozconfigs/linux32/release
@@ -5,16 +5,15 @@ if [ -n "$ENABLE_RELEASE_PROMOTION" ]; t
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
-
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
 export BUILDING_RELEASE=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/beta
+++ b/browser/config/mozconfigs/linux64/beta
@@ -2,12 +2,11 @@ if [ -n "$ENABLE_RELEASE_PROMOTION" ]; t
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
-
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/devedition
+++ b/browser/config/mozconfigs/linux64/devedition
@@ -12,13 +12,13 @@ ac_add_options --enable-verify-mar
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
 
 ac_add_options --with-branding=browser/branding/aurora
 
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 # Enable MOZ_ALLOW_LEGACY_EXTENSIONS
 ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/release
+++ b/browser/config/mozconfigs/linux64/release
@@ -6,15 +6,15 @@ if [ -n "$ENABLE_RELEASE_PROMOTION" ]; t
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
 export BUILDING_RELEASE=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/whitelist
+++ b/browser/config/mozconfigs/whitelist
@@ -37,16 +37,17 @@ whitelist['nightly']['linux64'] += [
     'export MOZ_TELEMETRY_REPORTING=1',
     "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
     'STRIP_FLAGS="--strip-debug"',
     'ac_add_options --with-ccache=/usr/bin/ccache',
 ]
 
 whitelist['nightly']['macosx64'] += [
     'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
+    'if test "${MOZ_UPDATE_CHANNEL}" = "nightly-try"; then',
     'ac_add_options --with-macbundlename-prefix=Firefox',
     'fi',
     'mk_add_options MOZ_MAKE_FLAGS="-j12"',
     'ac_add_options --with-ccache',
     'ac_add_options --disable-install-strip',
     'ac_add_options --enable-instruments',
     'ac_add_options --enable-dtrace',
     'if test `uname -s` != Linux; then',
@@ -69,27 +70,27 @@ for platform in all_platforms:
         'ac_add_options --enable-official-branding',
         'mk_add_options MOZ_MAKE_FLAGS="-j4"',
         'export BUILDING_RELEASE=1',
         'if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then',
         'MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}',
         'MOZ_AUTOMATION_UPDATE_PACKAGING=1',
         'fi',
     ]
-whitelist['release']['win32'] += ['mk_add_options MOZ_PGO=1']
-whitelist['release']['win64'] += ['mk_add_options MOZ_PGO=1']
+whitelist['release']['win32'] += ['ac_add_options MOZ_PGO=1']
+whitelist['release']['win64'] += ['ac_add_options MOZ_PGO=1']
 
 whitelist['release']['linux32'] += [
     'export MOZILLA_OFFICIAL=1',
     'export MOZ_TELEMETRY_REPORTING=1',
-    'mk_add_options MOZ_PGO=1',
+    'ac_add_options MOZ_PGO=1',
     "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
 ]
 whitelist['release']['linux64'] += [
     'export MOZILLA_OFFICIAL=1',
     'export MOZ_TELEMETRY_REPORTING=1',
-    'mk_add_options MOZ_PGO=1',
+    'ac_add_options MOZ_PGO=1',
     "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
 ]
 
 if __name__ == '__main__':
     import pprint
     pprint.pprint(whitelist)
--- a/browser/config/mozconfigs/win32/beta
+++ b/browser/config/mozconfigs/win32/beta
@@ -1,14 +1,13 @@
 if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
-mk_add_options MOZ_PGO=1
-
+ac_add_options MOZ_PGO=1
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/devedition
+++ b/browser/config/mozconfigs/win32/devedition
@@ -7,15 +7,14 @@ fi
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
 ac_add_options --with-branding=browser/branding/aurora
-
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 
 # Enable MOZ_ALLOW_LEGACY_EXTENSIONS
 ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/release
+++ b/browser/config/mozconfigs/win32/release
@@ -4,18 +4,17 @@
 if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
-mk_add_options MOZ_PGO=1
-
+ac_add_options MOZ_PGO=1
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
 export BUILDING_RELEASE=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/beta
+++ b/browser/config/mozconfigs/win64/beta
@@ -2,14 +2,13 @@ if [ -n "$ENABLE_RELEASE_PROMOTION" ]; t
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
 
-mk_add_options MOZ_PGO=1
-
+ac_add_options MOZ_PGO=1
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/devedition
+++ b/browser/config/mozconfigs/win64/devedition
@@ -8,15 +8,14 @@ fi
 . "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
 
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --enable-verify-mar
 
 ac_add_options --with-branding=browser/branding/aurora
-
-mk_add_options MOZ_PGO=1
+ac_add_options MOZ_PGO=1
 
 # Enable MOZ_ALLOW_LEGACY_EXTENSIONS
 ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/release
+++ b/browser/config/mozconfigs/win64/release
@@ -5,18 +5,17 @@ if [ -n "$ENABLE_RELEASE_PROMOTION" ]; t
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
 
-mk_add_options MOZ_PGO=1
-
+ac_add_options MOZ_PGO=1
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
 export BUILDING_RELEASE=1
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -426,34 +426,54 @@ FormAutofillParent.prototype = {
         Services.telemetry.scalarAdd("formautofill.addresses.fill_type_manual", 1);
       }
     }
   },
 
   async _onCreditCardSubmit(creditCard, target, timeStartedFillingMS) {
     // We'll show the credit card doorhanger if:
     //   - User applys autofill and changed
-    //   - User fills form manually
-    if (creditCard.guid &&
-        Object.keys(creditCard.record).every(key => creditCard.untouchedFields.includes(key))) {
-      // Add probe to record credit card autofill(without modification).
-      Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill", 1);
-      this._recordFormFillingTime("creditCard", "autofill", timeStartedFillingMS);
-      return;
-    }
+    //   - User fills form manually and the filling data is not duplicated to storage
+    if (creditCard.guid) {
+      let originalCCData = this.profileStorage.creditCards.get(creditCard.guid);
+      let unchanged = Object.keys(creditCard.record).every(field => {
+        if (creditCard.record[field] === "" && !originalCCData[field]) {
+          return true;
+        }
+        // Avoid updating the fields that users don't modify.
+        let untouched = creditCard.untouchedFields.includes(field);
+        if (untouched) {
+          creditCard.record[field] = originalCCData[field];
+        }
+        return untouched;
+      });
 
-    // Add the probe to record credit card manual filling or autofill but modified case.
-    if (creditCard.guid) {
+      if (unchanged) {
+        this.profileStorage.creditCards.notifyUsed(creditCard.guid);
+        // Add probe to record credit card autofill(without modification).
+        Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill", 1);
+        this._recordFormFillingTime("creditCard", "autofill", timeStartedFillingMS);
+        return;
+      }
+      // Add the probe to record credit card autofill with modification.
       Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill_modified", 1);
       this._recordFormFillingTime("creditCard", "autofill-update", timeStartedFillingMS);
     } else {
+      // Add the probe to record credit card manual filling.
       Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_manual", 1);
       this._recordFormFillingTime("creditCard", "manual", timeStartedFillingMS);
     }
 
+    // Early return if it's a duplicate data
+    let dupGuid = this.profileStorage.creditCards.getDuplicateGuid(creditCard.record);
+    if (dupGuid) {
+      this.profileStorage.creditCards.notifyUsed(dupGuid);
+      return;
+    }
+
     let state = await FormAutofillDoorhanger.show(target, "creditCard");
     if (state == "cancel") {
       return;
     }
 
     if (state == "disable") {
       Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
       return;
@@ -461,17 +481,28 @@ FormAutofillParent.prototype = {
 
     // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
     // APIs are refactored to be async functions (bug 1399367).
     if (!await MasterPassword.ensureLoggedIn()) {
       log.warn("User canceled master password entry");
       return;
     }
 
-    this.profileStorage.creditCards.add(creditCard.record);
+    let changedGUIDs = [];
+    // TODO: Autofill(with guid) case should show update doorhanger with update/create new.
+    // It'll be implemented in bug 1403881 and only avoid mergering for now.
+    if (creditCard.guid) {
+      changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
+    } else {
+      changedGUIDs.push(...this.profileStorage.creditCards.mergeToStorage(creditCard.record));
+      if (!changedGUIDs.length) {
+        changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
+      }
+    }
+    changedGUIDs.forEach(guid => this.profileStorage.creditCards.notifyUsed(guid));
   },
 
   _onFormSubmit(data, target) {
     let {profile: {address, creditCard}, timeStartedFillingMS} = data;
 
     if (address) {
       this._onAddressSubmit(address, target, timeStartedFillingMS);
     }
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1620,16 +1620,98 @@ class CreditCards extends AutofillRecord
         creditCard["cc-exp-month"] = expMonth;
         creditCard["cc-exp-year"] = expYear;
         break;
       }
     }
 
     delete creditCard["cc-exp"];
   }
+
+  /**
+   * Normailze the given record and retrun the first matched guid if storage has the same record.
+   * @param {Object} targetCreditCard
+   *        The credit card for duplication checking.
+   * @returns {string|null}
+   *          Return the first guid if storage has the same credit card and null otherwise.
+   */
+  getDuplicateGuid(targetCreditCard) {
+    let clonedTargetCreditCard = this._clone(targetCreditCard);
+    this._normalizeRecord(clonedTargetCreditCard);
+    for (let creditCard of this.data) {
+      let isDuplicate = this.VALID_FIELDS.every(field => {
+        if (!clonedTargetCreditCard[field]) {
+          return !creditCard[field];
+        }
+        if (field == "cc-number") {
+          return this._getMaskedCCNumber(clonedTargetCreditCard[field]) == creditCard[field];
+        }
+        return clonedTargetCreditCard[field] == creditCard[field];
+      });
+      if (isDuplicate) {
+        return creditCard.guid;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Merge new credit card into the specified record if cc-number is identical.
+   *
+   * @param  {string} guid
+   *         Indicates which credit card to merge.
+   * @param  {Object} creditCard
+   *         The new credit card used to merge into the old one.
+   * @returns {boolean}
+   *          Return true if credit card is merged into target with specific guid or false if not.
+   */
+  mergeIfPossible(guid, creditCard) {
+    this.log.debug("mergeIfPossible:", guid, creditCard);
+
+    // Query raw data for comparing the decrypted credit card number
+    let creditCardFound = this.get(guid, {rawData: true});
+    if (!creditCardFound) {
+      throw new Error("No matching credit card.");
+    }
+
+    let creditCardToMerge = this._cloneAndCleanUp(creditCard);
+    this._normalizeRecord(creditCardToMerge);
+
+    for (let field of this.VALID_FIELDS) {
+      let existingField = creditCardFound[field];
+
+      // Make sure credit card field is existed and have value
+      if (field == "cc-number" && (!existingField || !creditCardToMerge[field])) {
+        return false;
+      }
+
+      if (!creditCardToMerge[field]) {
+        creditCardToMerge[field] = existingField;
+      }
+
+      let incomingField = creditCardToMerge[field];
+      if (incomingField && existingField) {
+        if (incomingField != existingField) {
+          this.log.debug("Conflicts: field", field, "has different value.");
+          return false;
+        }
+      }
+    }
+
+    // Early return if the data is the same.
+    let exactlyMatch = this.VALID_FIELDS.every((field) =>
+      creditCardFound[field] === creditCardToMerge[field]
+    );
+    if (exactlyMatch) {
+      return true;
+    }
+
+    this.update(guid, creditCardToMerge, true);
+    return true;
+  }
 }
 
 function ProfileStorage(path) {
   this._path = path;
   this._initializePromise = null;
   this.INTERNAL_FIELDS = INTERNAL_FIELDS;
 }
 
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -40,32 +40,163 @@ add_task(async function test_submit_cred
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
         name.focus();
         name.setUserInput("User 1");
 
-        let number = form.querySelector("#cc-number");
-        number.setUserInput("1111222233334444");
+        form.querySelector("#cc-number").setUserInput("1111222233334444");
+        form.querySelector("#cc-exp-month").setUserInput("12");
+        form.querySelector("#cc-exp-year").setUserInput("2017");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
     }
   );
 
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
+  await removeAllRecords();
+});
+
+add_task(async function test_submit_untouched_creditCard_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      await openPopupOn(browser, "form #cc-name");
+      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+      await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await sleep(1000);
+      is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 1, "Still 1 credit card");
+  is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
+  await removeAllRecords();
+});
+
+add_task(async function test_submit_changed_subset_creditCard_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+
+        name.focus();
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        name.setUserInput("");
+
+        form.querySelector("#cc-number").setUserInput("1234567812345678");
+        form.querySelector("#cc-exp-month").setUserInput("4");
+        form.querySelector("#cc-exp-year").setUserInput("2017");
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MAIN_BUTTON);
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 1, "Still 1 credit card in storage");
+  is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"], "name field still exists");
+  await removeAllRecords();
+});
+
+add_task(async function test_submit_duplicate_creditCard_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+
+        name.setUserInput("John Doe");
+        form.querySelector("#cc-number").setUserInput("1234567812345678");
+        form.querySelector("#cc-exp-month").setUserInput("4");
+        form.querySelector("#cc-exp-year").setUserInput("2017");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await sleep(1000);
+      is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 1, "Still 1 credit card in storage");
+  is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"], "Verify the name field");
+  is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
+  await removeAllRecords();
+});
+
+add_task(async function test_submit_unnormailzed_creditCard_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+
+        name.setUserInput("John Doe");
+        form.querySelector("#cc-number").setUserInput("1234567812345678");
+        form.querySelector("#cc-exp-month").setUserInput("4");
+        // Set unnormalized year
+        form.querySelector("#cc-exp-year").setUserInput("17");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await sleep(1000);
+      is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 1, "Still 1 credit card in storage");
+  is(creditCards[0]["cc-exp-year"], "2017", "Verify the expiry year field");
+  await removeAllRecords();
 });
 
 add_task(async function test_submit_creditCard_never_save() {
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
       await ContentTask.spawn(browser, null, async function() {
@@ -86,17 +217,17 @@ add_task(async function test_submit_cred
       await promiseShown;
       await clickDoorhangerButton(MENU_BUTTON, 0);
     }
   );
 
   await sleep(1000);
   let creditCards = await getCreditCards();
   let creditCardPref = SpecialPowers.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
-  is(creditCards.length, 1, "Still 1 credit card in storage");
+  is(creditCards.length, 0, "No credit card in storage");
   is(creditCardPref, false, "Credit card is disabled");
   SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
 });
 
 add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
   LoginTestUtils.masterPassword.enable();
   // Login with the masterPassword in LoginTestUtils.
   let masterPasswordDialogShown = waitForMasterPasswordDialog(true);
@@ -122,20 +253,21 @@ add_task(async function test_submit_cred
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
       await masterPasswordDialogShown;
       await TestUtils.topicObserved("formautofill-storage-changed");
     }
   );
 
   let creditCards = await getCreditCards();
-  is(creditCards.length, 2, "2 credit cards in storage");
-  is(creditCards[1]["cc-name"], "User 0", "Verify the name field");
-  is(creditCards[1]["cc-number"], "************1234", "Verify the card number field");
+  is(creditCards.length, 1, "1 credit card in storage");
+  is(creditCards[0]["cc-name"], "User 0", "Verify the name field");
+  is(creditCards[0]["cc-number"], "************1234", "Verify the card number field");
   LoginTestUtils.masterPassword.disable();
+  await removeAllRecords();
 });
 
 add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
   LoginTestUtils.masterPassword.enable();
   let masterPasswordDialogShown = waitForMasterPasswordDialog();
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
@@ -158,17 +290,17 @@ add_task(async function test_submit_cred
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
       await masterPasswordDialogShown;
     }
   );
 
   await sleep(1000);
   let creditCards = await getCreditCards();
-  is(creditCards.length, 2, "Still 2 credit cards in storage");
+  is(creditCards.length, 0, "No credit cards in storage");
   LoginTestUtils.masterPassword.disable();
 });
 
 add_task(async function test_submit_creditCard_with_sync_account() {
   await SpecialPowers.pushPrefEnv({
     "set": [
       [SYNC_USERNAME_PREF, "foo@bar.com"],
       [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
@@ -212,17 +344,17 @@ add_task(async function test_submit_cred
       is(secondaryButton.disabled, true, "Not saving button should be disabled");
       is(menuButton.disabled, true, "Never saving menu button should be disabled");
       // Click the checkbox again to disable credit card sync.
       cb.click();
       is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), false,
          "creditCards sync should be disabled after unchecked");
       is(secondaryButton.disabled, false, "Not saving button should be enabled again");
       is(menuButton.disabled, false, "Never saving menu button should be enabled again");
-      await clickDoorhangerButton(MAIN_BUTTON);
+      await clickDoorhangerButton(SECONDARY_BUTTON);
     }
   );
 });
 
 add_task(async function test_submit_creditCard_with_synced_already() {
   await SpecialPowers.pushPrefEnv({
     "set": [
       [SYNC_CREDITCARDS_PREF, true],
@@ -247,12 +379,45 @@ add_task(async function test_submit_cred
         // Wait 500ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 500));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       let cb = getDoorhangerCheckbox();
       ok(cb.hidden, "Sync checkbox should be hidden");
+      await clickDoorhangerButton(SECONDARY_BUTTON);
+    }
+  );
+});
+
+add_task(async function test_submit_manual_mergeable_creditCard_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_3);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+
+        name.setUserInput("User 3");
+        form.querySelector("#cc-number").setUserInput("9999888877776666");
+        form.querySelector("#cc-exp-month").setUserInput("1");
+        form.querySelector("#cc-exp-year").setUserInput("2000");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+      await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
     }
   );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 1, "Still 1 credit card in storage");
+  is(creditCards[0]["cc-name"], "User 3", "Verify the name field");
+  await removeAllRecords();
 });
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,30 +1,30 @@
 /* exported MANAGE_ADDRESSES_DIALOG_URL, MANAGE_CREDIT_CARDS_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL,
             BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
             TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
             FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
             SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, SYNC_CREDITCARDS_PREF, SYNC_CREDITCARDS_AVAILABLE_PREF,
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
             getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
-            getNotification, getDoorhangerButton */
+            getNotification, getDoorhangerButton, removeAllRecords */
 
 "use strict";
 
 Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
 
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
 const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
 const CREDITCARD_FORM_URL =
-  "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
+  "https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
 const FTU_PREF = "extensions.formautofill.firstTimeUse";
 const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
 const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available";
 const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
 const SYNC_USERNAME_PREF = "services.sync.username";
 const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
 const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
 const SYNC_CREDITCARDS_AVAILABLE_PREF = "services.sync.engine.creditcards.available";
@@ -282,19 +282,21 @@ function waitForMasterPasswordDialog(log
       dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
       dialog.ui.button0.click();
     } else {
       dialog.ui.button1.click();
     }
   });
 }
 
-registerCleanupFunction(async function() {
+async function removeAllRecords() {
   let addresses = await getAddresses();
   if (addresses.length) {
     await removeAddresses(addresses.map(address => address.guid));
   }
 
   let creditCards = await getCreditCards();
   if (creditCards.length) {
     await removeCreditCards(creditCards.map(cc => cc.guid));
   }
-});
+}
+
+registerCleanupFunction(removeAllRecords);
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -23,16 +23,21 @@ const TEST_CREDIT_CARD_2 = {
 };
 
 const TEST_CREDIT_CARD_3 = {
   "cc-number": "9999888877776666",
   "cc-exp-month": 1,
   "cc-exp-year": 2000,
 };
 
+const TEST_CREDIT_CARD_4 = {
+  "cc-name": "Foo Bar",
+  "cc-number": "9999888877776666",
+};
+
 const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
   "cc-name": "",
   "cc-number": "1234123412341234",
   "cc-exp-month": 1,
 };
 
 const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
   "cc-number": "1234123412341234",
@@ -53,16 +58,78 @@ const TEST_CREDIT_CARD_WITH_INVALID_EXPI
   "cc-exp-year": -3,
 };
 
 const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
   "cc-name": "John Doe",
   "cc-number": "1111 2222 3333 4444",
 };
 
+const MERGE_TESTCASES = [
+  {
+    description: "Merge a superset",
+    creditCardInStorage: {
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+    creditCardToMerge: {
+      "cc-name": "John Doe",
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+    expectedCreditCard: {
+      "cc-name": "John Doe",
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+  },
+  {
+    description: "Merge a subset",
+    creditCardInStorage: {
+      "cc-name": "John Doe",
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+    creditCardToMerge: {
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+    expectedCreditCard: {
+      "cc-name": "John Doe",
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+    noNeedToUpdate: true,
+  },
+  {
+    description: "Merge an creditCard with partial overlaps",
+    creditCardInStorage: {
+      "cc-name": "John Doe",
+      "cc-number": "1234567812345678",
+    },
+    creditCardToMerge: {
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+    expectedCreditCard: {
+      "cc-name": "John Doe",
+      "cc-number": "1234567812345678",
+      "cc-exp-month": 4,
+      "cc-exp-year": 2017,
+    },
+  },
+];
+
 let prepareTestCreditCards = async function(path) {
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
   do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_1));
   await onChanged;
@@ -310,8 +377,86 @@ add_task(async function test_remove() {
   await profileStorage.initialize();
 
   creditCards = profileStorage.creditCards.getAll();
 
   do_check_eq(creditCards.length, 1);
 
   do_check_eq(profileStorage.creditCards.get(guid), null);
 });
+
+MERGE_TESTCASES.forEach((testcase) => {
+  add_task(async function test_merge() {
+    do_print("Starting testcase: " + testcase.description);
+    let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+                                                  [testcase.creditCardInStorage],
+                                                  "creditCards");
+    let creditCards = profileStorage.creditCards.getAll();
+    let guid = creditCards[0].guid;
+    let timeLastModified = creditCards[0].timeLastModified;
+    // Merge creditCard and verify the guid in notifyObservers subject
+    let onMerged = TestUtils.topicObserved(
+      "formautofill-storage-changed",
+      (subject, data) =>
+        data == "update" && subject.QueryInterface(Ci.nsISupportsString).data == guid
+    );
+    // Force to create sync metadata.
+    profileStorage.creditCards.pullSyncChanges();
+    do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
+    Assert.ok(profileStorage.creditCards.mergeIfPossible(guid, testcase.creditCardToMerge));
+    if (!testcase.noNeedToUpdate) {
+      await onMerged;
+    }
+    creditCards = profileStorage.creditCards.getAll();
+    Assert.equal(creditCards.length, 1);
+    do_check_credit_card_matches(creditCards[0], testcase.expectedCreditCard);
+    if (!testcase.noNeedToUpdate) {
+      // Record merging should update timeLastModified and bump the change counter.
+      Assert.notEqual(creditCards[0].timeLastModified, timeLastModified);
+      do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 2);
+    } else {
+      // Subset record merging should not update timeLastModified and the change
+      // counter is still the same.
+      Assert.equal(creditCards[0].timeLastModified, timeLastModified);
+      do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
+    }
+  });
+});
+
+add_task(async function test_merge_unable_merge() {
+  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+                                                [TEST_CREDIT_CARD_1],
+                                                "creditCards");
+
+  let creditCards = profileStorage.creditCards.getAll();
+  let guid = creditCards[0].guid;
+  // Force to create sync metadata.
+  profileStorage.creditCards.pullSyncChanges();
+  do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
+
+  // Unable to merge because of conflict
+  let anotherCreditCard = profileStorage.creditCards._clone(TEST_CREDIT_CARD_1);
+  anotherCreditCard["cc-name"] = "Foo Bar";
+  do_check_eq(profileStorage.creditCards.mergeIfPossible(guid, anotherCreditCard), false);
+  // The change counter is unchanged.
+  do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
+
+  // Unable to merge because no credit card number
+  anotherCreditCard = profileStorage.creditCards._clone(TEST_CREDIT_CARD_1);
+  anotherCreditCard["cc-number"] = "";
+  do_check_eq(profileStorage.creditCards.mergeIfPossible(guid, anotherCreditCard), false);
+  // The change counter is still unchanged.
+  do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
+});
+
+add_task(async function test_mergeToStorage() {
+  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+                                                [TEST_CREDIT_CARD_3, TEST_CREDIT_CARD_4],
+                                                "creditCards");
+  // Merge a creditCard to storage
+  let anotherCreditCard = profileStorage.creditCards._clone(TEST_CREDIT_CARD_3);
+  anotherCreditCard["cc-name"] = "Foo Bar";
+  do_check_eq(profileStorage.creditCards.mergeToStorage(anotherCreditCard).length, 2);
+  do_check_eq(profileStorage.creditCards.getAll()[0]["cc-name"], "Foo Bar");
+  do_check_eq(profileStorage.creditCards.getAll()[0]["cc-exp"], "2000-01");
+  do_check_eq(profileStorage.creditCards.getAll()[1]["cc-name"], "Foo Bar");
+  do_check_eq(profileStorage.creditCards.getAll()[1]["cc-exp"], "2000-01");
+});
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -38,22 +38,22 @@ const PREF_WHITELIST = [
   "onboarding-tour-singlesearch",
   "onboarding-tour-sync",
 ].forEach(tourId => PREF_WHITELIST.push([`browser.onboarding.tour.${tourId}.completed`, PREF_BOOL]));
 
 let waitingForBrowserReady = true;
 let startupData;
 
 /**
- * Set pref. Why no `getPrefs` function is due to the priviledge level.
+ * Set pref. Why no `getPrefs` function is due to the privilege level.
  * We cannot set prefs inside a framescript but can read.
- * For simplicity and effeciency, we still read prefs inside the framescript.
+ * For simplicity and efficiency, we still read prefs inside the framescript.
  *
  * @param {Array} prefs the array of prefs to set.
- *   The array element carrys info to set pref, should contain
+ *   The array element carries info to set pref, should contain
  *   - {String} name the pref name, such as `browser.onboarding.state`
  *   - {*} value the value to set
  **/
 function setPrefs(prefs) {
   prefs.forEach(pref => {
     let prefObj = PREF_WHITELIST.find(([name, ]) => name == pref.name);
     if (!prefObj) {
       return;
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -26,17 +26,17 @@ const SPEECH_BUBBLE_NEWTOUR_STRING_ID = 
 const SPEECH_BUBBLE_UPDATETOUR_STRING_ID = "onboarding.overlay-icon-tooltip-updated2";
 const ICON_STATE_WATERMARK = "watermark";
 const ICON_STATE_DEFAULT = "default";
 /**
  * Add any number of tours, key is the tourId, value should follow the format below
  * "tourId": { // The short tour id which could be saved in pref
  *   // The unique tour id
  *   id: "onboarding-tour-addons",
- *   // (optional) mark tour as complete instantly when user enters the tour
+ *   // (optional) mark tour as complete instantly when the user enters the tour
  *   instantComplete: false,
  *   // The string id of tour name which would be displayed on the navigation bar
  *   tourNameId: "onboarding.tour-addon",
  *   // The method returing strings used on tour notification
  *   getNotificationStrings(bundle):
  *     - title: // The string of tour notification title
  *     - message: // The string of tour notification message
  *     - button: // The string of tour notification action button title
@@ -367,17 +367,17 @@ function registerNewTelemetrySession(dat
 class Onboarding {
   constructor(contentWindow) {
     this.init(contentWindow);
   }
 
   async init(contentWindow) {
     this._window = contentWindow;
     // session_key is used for telemetry to track the current tab.
-    // The number will renew after reload the page.
+    // The number will renew after reloading the page.
     this._session_key = Date.now();
     this._tours = [];
     this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update");
 
     let tourIds = this._getTourIDList();
     tourIds.forEach(tourId => {
       if (onboardingTourset[tourId]) {
         this._tours.push(onboardingTourset[tourId]);
@@ -392,17 +392,17 @@ class Onboarding {
     // no flash of style changes and no additional reflow.
     await this._loadCSS();
     this._bundle = Services.strings.createBundle(BUNDLE_URI);
 
     this._loadJS(UITOUR_JS_URI);
 
     this._window.addEventListener("resize", this);
 
-    // Destroy on unload. This is to ensure we remove all the stuff we left.
+    // Destroy on unloading. This is to ensure we remove all the stuff we left.
     // No any leak out there.
     this._window.addEventListener("unload", () => this.destroy());
 
     this.uiInitialized = false;
     this._resizeTimerId =
       this._window.requestIdleCallback(() => this._resizeUI());
     registerNewTelemetrySession({
       page: this._window.location.href,
@@ -463,17 +463,17 @@ class Onboarding {
     let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, "");
     return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
   }
 
   _initNotification() {
     let doc = this._window.document;
     if (doc.hidden) {
       // When the preloaded-browser feature is on,
-      // it would preload an hidden about:newtab in the background.
+      // it would preload a hidden about:newtab in the background.
       // We don't want to show notification in that hidden state.
       let onVisible = () => {
         if (!doc.hidden) {
           doc.removeEventListener("visibilitychange", onVisible);
           this.showNotification();
         }
       };
       doc.addEventListener("visibilitychange", onVisible);
@@ -590,19 +590,22 @@ class Onboarding {
         event: "overlay-cta-click",
         tour_id: activeItem.id,
         session_key: this._session_key,
       });
     }
   }
 
   /**
-   * Wrap keyboard focus within the dialog and focus on first element after last
-   * when moving forward or last element after first when moving backwards. Do
-   * nothing if focus is moving in the middle of the list of dialog's focusable
+   * Wrap keyboard focus within the dialog.
+   * When moving forward, focus on the first element when the current focused
+   * element is the last one.
+   * When moving backward, focus on the last element when the current focused
+   * element is the first one.
+   * Do nothing if focus is moving in the middle of the list of dialog's focusable
    * elements.
    *
    * @param  {DOMNode} current  currently focused element
    * @param  {Boolean} back     direction
    * @return {DOMNode}          newly focused element if any
    */
   wrapMoveFocus(current, back) {
     let elms = [...this._dialog.querySelectorAll(
@@ -628,17 +631,17 @@ class Onboarding {
         // Remember that the dialog was opened with a keyboard.
         this._overlayIcon.dataset.keyboardFocus = true;
         this.handleClick(target);
         event.preventDefault();
       }
       return;
     }
 
-    // Current focused item can be tab container if previous navigation was done
+    // Currently focused item could be tab container if previous navigation was done
     // via mouse.
     if (target.classList.contains("onboarding-tour-item-container")) {
       target = target.firstChild;
     }
     let targetIndex;
     switch (key) {
       case " ":
       case "Enter":
@@ -765,30 +768,30 @@ class Onboarding {
    */
   toggleModal(opened) {
     let { document: doc } = this._window;
     if (opened) {
       // Set aria-hidden to true for the rest of the document.
       [...doc.body.children].forEach(
         child => child.id !== "onboarding-overlay" &&
                  child.setAttribute("aria-hidden", true));
-      // When dialog is opened with the keyboard, focus on the 1st uncomplete tour
-      // because it will be the selected tour
+      // When dialog is opened with the keyboard, focus on the first
+      // uncomplete tour because it will be the selected tour.
       if (this._overlayIcon.dataset.keyboardFocus) {
         doc.getElementById(this._firstUncompleteTour.id).focus();
       } else {
-        // When dialog is opened with mouse, focus on the dialog itself to avoid
-        // visible keyboard focus styling.
+        // When the dialog is opened with the mouse, focus on the dialog
+        // itself to avoid visible keyboard focus styling.
         this._dialog.focus();
       }
     } else {
       // Remove all set aria-hidden attributes.
       [...doc.body.children].forEach(
         child => child.removeAttribute("aria-hidden"));
-      // If dialog was opened with a keyboard, set the focus back on the overlay
+      // If dialog was opened with a keyboard, set the focus back to the overlay
       // button.
       if (this._overlayIcon.dataset.keyboardFocus) {
         delete this._overlayIcon.dataset.keyboardFocus;
         this._overlayIcon.focus();
       } else {
         this._window.document.activeElement.blur();
       }
     }
@@ -809,20 +812,20 @@ class Onboarding {
         tab.classList.add("onboarding-active");
         tab.setAttribute("aria-selected", true);
         telemetry({
           event: "overlay-nav-click",
           tour_id: tourId,
           session_key: this._session_key,
         });
 
-        // some tours should completed instantly upon showing.
+        // Some tours should complete instantly upon showing.
         if (tab.getAttribute("data-instant-complete")) {
           this.setToursCompleted([tourId]);
-          // also track auto completed tour so we can filter data with the same event
+          // Also track auto-completed tour so we can filter data with the same event.
           telemetry({
             event: "overlay-cta-click",
             tour_id: tourId,
             session_key: this._session_key,
           });
         }
       } else {
         tab.classList.remove("onboarding-active");
@@ -1001,17 +1004,17 @@ class Onboarding {
       queue = [];
     }
 
     let startQueueLength = queue.length;
     // See if need to move on to the next tour
     if (queue.length > 0 && this._isTimeForNextTourNotification(lastTime)) {
       queue.shift();
     }
-    // We don't want to prompt completed tour.
+    // We don't want to prompt the completed tour.
     while (queue.length > 0 && this.isTourCompleted(queue[0])) {
       queue.shift();
     }
 
     if (queue.length == 0) {
       sendMessageToChrome("set-prefs", [
         {
           name: "browser.onboarding.notification.finished",
@@ -1237,17 +1240,17 @@ class Onboarding {
       let div = tour.getPage(this._window, this._bundle);
 
       // Do a traverse for elements in the page that need to be localized.
       let l10nElements = div.querySelectorAll("[data-l10n-id]");
       for (let i = 0; i < l10nElements.length; i++) {
         let element = l10nElements[i];
         // We always put brand short name as the first argument for it's the
         // only and frequently used arguments in our l10n case. Rewrite it if
-        // other arguments appears.
+        // other arguments appear.
         element.textContent = this._bundle.formatStringFromName(
                                 element.dataset.l10nId, [BRAND_SHORT_NAME], 1);
       }
 
       div.id = tourPanelId;
       div.classList.add("onboarding-tour-page");
       div.setAttribute("role", "tabpanel");
       div.setAttribute("aria-labelledby", tour.id);
@@ -1294,17 +1297,17 @@ if (Services.prefs.getBoolPref("browser.
   addEventListener("load", function onLoad(evt) {
     if (!content || evt.target != content.document) {
       return;
     }
 
     let window = evt.target.defaultView;
     let location = window.location.href;
     if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
-      // We just want to run tests as quick as possible
+      // We just want to run tests as quickly as possible
       // so in the automation test, we don't do `requestIdleCallback`.
       if (Cu.isInAutomation) {
         new Onboarding(window);
         return;
       }
       window.requestIdleCallback(() => {
         new Onboarding(window);
       });
--- a/browser/extensions/onboarding/data_events.md
+++ b/browser/extensions/onboarding/data_events.md
@@ -61,25 +61,25 @@ For reference, Onyx is a Mozilla owned s
 }
 ```
 
 
 | KEY | DESCRIPTION | &nbsp; |
 |-----|-------------|:-----:|
 | `addon_version` | [Required] The version of the Onboarding addon. | :one:
 | `category` | [Required] Either ("overlay-interactions", "notification-interactions") to identify which kind of the interaction | :one:
-| `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.jsm) module to provide an identifier for this device. Auto append by `ping-centre` module | :one:
+| `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.jsm) module to provide an identifier for this device. This data is automatically appended by `ping-centre` module | :one:
 | `event` | [Required] The type of event. allowed event strings are defined in the below section | :one:
 | `impression` | [Optional] An integer to record how many times the current notification tour is shown to the user. Each Notification tour can show not more than 8 times. We put `-1` when this field is not relevant to this event | :one:
-| `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two:
-| `locale` | The browser chrome's language (eg. en-US). | :two:
+| `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo-information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two:
+| `locale` | The browser chrome's language (e.g. en-US). | :two:
 | `page` | [Required] One of ["about:newtab", "about:home"]| :one:
 | `session_begin` | Timestamp in (integer) milliseconds when onboarding/overlay/notification becoming visible. | :one:
 | `session_end` | Timestamp in (integer) milliseconds when onboarding/overlay/notification losing focus. | :one:
-| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session when onboarding is inited/when overlay is opened/when notification is shown. | :one:
+| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session when onboarding is inited/when the overlay is opened/when notification is shown. | :one:
 | `timestamp` | Timestamp in (integer) milliseconds when the event triggered | :one:
 | `tour_id` | id of the current tour. The number of open from notification can be retrieved via 'notification-cta-click event'. We put ` ` when this field is not relevant to this event | :one:
 | `tour_source` | [Required] One of ["default", "watermark"] indicates the overlay is opened while in the default or the watermark state. Open from the notification bar is counted via 'notification-cta-click event'. | :one:
 | `tour_type` | [Required] One of ["new", "update"] indicates the user is a `new` user or the `update` user upgrade from the older version | :one:
 | `ua` | [Auto populated by Onyx] The user agent string. | :two:
 | `ver` | [Auto populated by Onyx] The version of the Onyx API the ping was sent to. | :one:
 
 **Where:**
@@ -91,32 +91,31 @@ For reference, Onyx is a Mozilla owned s
 
 Here are all allowed `event` strings that defined in `OnboardingTelemetry::EVENT_WHITELIST`.
 ### Session events
 
 | EVENT | DESCRIPTION |
 |-----------|---------------------|
 | `onboarding-register-session` | internal event triggered to register a new page session. Called when the onboarding script is inited in a browser tab. Will not send out any data. |
 | `onboarding-session-begin` | internal event triggered when the onboarding script is inited, will not send out any data. |
-| `onboarding-session-end` | internal event triggered when the onboarding script is destoryed. `onboarding-session` event is sent to the server. |
-| `onboarding-session` | event is sent when the onboarding script is destoryed |
+| `onboarding-session-end` | internal event triggered when the onboarding script is destroyed. `onboarding-session` event is sent to the server. |
+| `onboarding-session` | event is sent when the onboarding script is destroyed |
 
 ### Overlay events
 
 | EVENT | DESCRIPTION |
 |-----------|---------------------|
 | `overlay-session-begin` | internal event triggered when user open the overlay, will not send out any data. |
-| `overlay-session-end` | internal event is triggered when user close the overlay. `overlay-session` event is sent to the server. |
+| `overlay-session-end` | internal event is triggered when user closes the overlay. `overlay-session` event is sent to the server. |
 | `overlay-session` | event is sent when user close the overlay |
-| `overlay-nav-click` | event is sent when click or auto select the overlay navigate item |
+| `overlay-nav-click` | event is sent when clicking or auto select the overlay navigate item |
 | `overlay-cta-click` | event is sent when user click the overlay CTA button |
-| `overlay-skip-tour` | event is sent when click the overlay `skip tour` button |
+| `overlay-skip-tour` | event is sent when clicking the overlay `skip tour` button |
 
 ### Notification events
 
 | EVENT | DESCRIPTION |
 |-----------|---------------------|
 | `notification-session-begin` | internal event triggered when user open the notification, will not send out any data. |
-| `notification-session-end` | internal event is triggered when user close the notification. `notification-session` event is sent to the server. |
-| `notification-session` | event is sent when user close the notification |
-| `notification-close-button-click` | event is sent when click the notification close button |
-| `notification-cta-click` | event is sent when click the notification CTA button |
-
+| `notification-session-end` | internal event is triggered when user closes the notification. `notification-session` event is sent to the server. |
+| `notification-session` | event is sent when user closes the notification |
+| `notification-close-button-click` | event is sent when clicking the notification close button |
+| `notification-cta-click` | event is sent when clicking the notification CTA button |
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js
@@ -41,31 +41,16 @@ add_task(async function test_clean_up_ui
 
   // Close the overlay by clicking the overlay
   let highlightClosePromise = promisePopupChange(highlight, "closed");
   BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser);
   await promiseOnboardingOverlayClosed(tab.linkedBrowser);
   await highlightClosePromise;
   is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay");
 
-  // Trigger UITour showHighlight
-  highlightOpenPromise = promisePopupChange(highlight, "open");
-  await triggerUITourHighlight("library", tab);
-  await highlightOpenPromise;
-  is(highlight.state, "open", "Should show UITour highlight");
-  is(highlight.getAttribute("targetName"), "library", "UITour should highlight library");
-
-  // Close the overlay by clicking the overlay
-  // Should not click the close button here since the close button is hovered by appmenu and can't be clicked on win7
-  highlightClosePromise = promisePopupChange(highlight, "closed");
-  BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser);
-  await promiseOnboardingOverlayClosed(tab.linkedBrowser);
-  await highlightClosePromise;
-  is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay");
-
   // Trigger UITour showHighlight again
   highlightOpenPromise = promisePopupChange(highlight, "open");
   await triggerUITourHighlight("library", tab);
   await highlightOpenPromise;
   is(highlight.state, "open", "Should show UITour highlight");
   is(highlight.getAttribute("targetName"), "library", "UITour should highlight library");
 
   // Close the overlay by clicking the skip-tour button
--- a/build/build-clang/clang-4-linux64.json
+++ b/build/build-clang/clang-4-linux64.json
@@ -11,11 +11,12 @@
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_401/final",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
       "llvm-debug-frame.patch",
-      "hide-gcda-profiling-symbols.patch"
+      "hide-gcda-profiling-symbols.patch",
+      "pr_set_ptracer.patch"
     ]
 }
--- a/build/build-clang/clang-5-linux64.json
+++ b/build/build-clang/clang-5-linux64.json
@@ -11,11 +11,12 @@
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_500/final",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
       "llvm-debug-frame-for-5.patch",
-      "r313872.patch"
+      "r313872.patch",
+      "pr_set_ptracer.patch"
     ]
 }
new file mode 100644
--- /dev/null
+++ b/build/build-clang/pr_set_ptracer.patch
@@ -0,0 +1,27 @@
+--- a/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc
++++ b/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc
+@@ -57,6 +57,14 @@
+ #include "sanitizer_mutex.h"
+ #include "sanitizer_placement_new.h"
+ 
++// Sufficiently old kernel headers don't provide this value, but we can still
++// call prctl with it. If the runtime kernel is new enough, the prctl call will
++// have the desired effect; if the kernel is too old, the call will error and we
++// can ignore said error.
++#ifndef PR_SET_PTRACER
++#define PR_SET_PTRACER 0x59616d61
++#endif
++
+ // This module works by spawning a Linux task which then attaches to every
+ // thread in the caller process with ptrace. This suspends the threads, and
+ // PTRACE_GETREGS can then be used to obtain their register state. The callback
+@@ -433,9 +441,7 @@
+     ScopedSetTracerPID scoped_set_tracer_pid(tracer_pid);
+     // On some systems we have to explicitly declare that we want to be traced
+     // by the tracer thread.
+-#ifdef PR_SET_PTRACER
+     internal_prctl(PR_SET_PTRACER, tracer_pid, 0, 0, 0);
+-#endif
+     // Allow the tracer thread to start.
+     tracer_thread_argument.mutex.Unlock();
+     // NOTE: errno is shared between this thread and the tracer thread.
--- a/build/compare-mozconfig/compare-mozconfigs-wrapper.py
+++ b/build/compare-mozconfig/compare-mozconfigs-wrapper.py
@@ -3,43 +3,38 @@
 # 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/.
 
 from __future__ import unicode_literals
 
 import logging
 import mozunit
 import subprocess
-import sys
 import unittest
 
 from os import path
 from buildconfig import substs
 
 log = logging.getLogger(__name__)
 
-def determine_platform():
-    platform_mapping = {'WINNT': {'x86_64': 'win64',
-                                  'i686': 'win32'},
-                        'Darwin': {'x86_64': 'macosx64'},
-                        'Linux': {'x86_64': 'linux64',
-                                  'i686': 'linux32'}}
-
-    os_type = substs['OS_TARGET']
-    cpu_type = substs['TARGET_CPU']
-    return platform_mapping.get(os_type, {}).get(cpu_type, None)
+PLATFORMS = (
+    'linux32',
+    'linux64',
+    'macosx64',
+    'win32',
+    'win64',
+)
 
 
 class TestCompareMozconfigs(unittest.TestCase):
     def test_compare_mozconfigs(self):
         """ A wrapper script that calls compare-mozconfig.py
         based on the platform that the machine is building for"""
-        platform = determine_platform()
-
-        if platform is not None:
+        for platform in PLATFORMS:
+            log.info('Comparing platform %s' % platform)
             python_exe = substs['PYTHON']
             topsrcdir = substs['top_srcdir']
 
             # construct paths and args for compare-mozconfig
             browser_dir = path.join(topsrcdir, 'browser')
             script_path = path.join(topsrcdir, 'build/compare-mozconfig/compare-mozconfigs.py')
             whitelist_path = path.join(browser_dir, 'config/mozconfigs/whitelist')
             beta_mozconfig_path = path.join(browser_dir, 'config/mozconfigs', platform, 'beta')
@@ -48,17 +43,16 @@ class TestCompareMozconfigs(unittest.Tes
 
             log.info("Comparing beta against nightly mozconfigs")
             ret_code = subprocess.call([python_exe, script_path, '--whitelist',
                                         whitelist_path, '--no-download',
                                         platform + ',' + beta_mozconfig_path +
                                         ',' + nightly_mozconfig_path])
             self.assertEqual(0, ret_code)
 
-
             log.info("Comparing release against nightly mozconfigs")
             ret_code = subprocess.call([python_exe, script_path, '--whitelist',
                                         whitelist_path, '--no-download',
                                         platform + ',' + release_mozconfig_path +
                                         ',' + nightly_mozconfig_path])
             self.assertEqual(0, ret_code)
 
 if __name__ == '__main__':
--- a/build/compare-mozconfig/compare-mozconfigs.py
+++ b/build/compare-mozconfig/compare-mozconfigs.py
@@ -3,18 +3,16 @@
 # 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/.
 
 # originally from https://hg.mozilla.org/build/tools/file/4ab9c1a4e05b/scripts/release/compare-mozconfigs.py
 
 from __future__ import unicode_literals
 
 import logging
-import os
-import site
 import sys
 import urllib2
 import difflib
 
 FAILURE_CODE = 1
 SUCCESS_CODE = 0
 
 log = logging.getLogger(__name__)
--- a/build/docs/pgo.rst
+++ b/build/docs/pgo.rst
@@ -7,20 +7,24 @@ Profile Guided Optimization
 :abbr:`PGO (Profile Guided Optimization)` is the process of adding
 probes to a compiled binary, running said binary, then using the
 run-time information to *recompile* the binary to (hopefully) make it
 faster.
 
 How PGO Builds Work
 ===================
 
-The supported interface for invoking a PGO build is to invoke the build system
-with ``MOZ_PGO`` defined. e.g.::
+The supported interface for invoking a PGO build is to add ``MOZ_PGO=1`` to
+configure flags and then build. e.g. in your mozconfig::
 
-    $ MOZ_PGO=1 ./mach build
+    ac_add_options MOZ_PGO=1
+
+Then::
+
+    $ ./mach build
 
 This is roughly equivalent to::
 
 #. Perform a build with *MOZ_PROFILE_GENERATE=1* and *MOZ_PGO_INSTRUMENTED=1*
 #. Package with *MOZ_PGO_INSTRUMENTED=1*
 #. Performing a run of the instrumented binaries
 #. $ make maybe_clobber_profiledbuild
 #. Perform a build with *MOZ_PROFILE_USE=1*
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -9,16 +9,17 @@ include('checks.configure')
 
 option(env='DIST', nargs=1, help='DIST directory')
 
 # Do not allow objdir == srcdir builds.
 # ==============================================================
 
 
 @depends('--help', 'DIST')
+@imports(_from='__builtin__', _import='open')
 @imports(_from='os.path', _import='exists')
 def check_build_environment(help, dist):
     topobjdir = os.path.realpath(os.path.abspath('.'))
     topsrcdir = os.path.realpath(os.path.abspath(
         os.path.join(os.path.dirname(__file__), '..', '..')))
 
     if dist:
         dist = normsep(dist[0])
@@ -29,28 +30,66 @@ def check_build_environment(help, dist):
         topsrcdir=topsrcdir,
         topobjdir=topobjdir,
         dist=dist,
     )
 
     if help:
         return result
 
+    # This limitation has mostly to do with GNU make. Since make can't represent
+    # variables with spaces without correct quoting and many paths are used
+    # without proper quoting, using paths with spaces commonly results in
+    # targets or dependencies being treated as multiple paths. This, of course,
+    # undermines the ability for make to perform up-to-date checks and makes
+    # the build system not work very efficiently. In theory, a non-make build
+    # backend will make this limitation go away. But there is likely a long tail
+    # of things that will need fixing due to e.g. lack of proper path quoting.
+    if len(topsrcdir.split()) > 1:
+        die('Source directory cannot be located in a path with spaces: %s' %
+            topsrcdir)
+    if len(topobjdir.split()) > 1:
+        die('Object directory cannot be located in a path with spaces: %s' %
+            topobjdir)
+
     if topsrcdir == topobjdir:
         die('  ***\n'
             '  * Building directly in the main source directory is not allowed.\n'
             '  *\n'
             '  * To build, you must run configure from a separate directory\n'
             '  * (referred to as an object directory).\n'
             '  *\n'
             '  * If you are building with a mozconfig, you will need to change your\n'
             '  * mozconfig to point to a different object directory.\n'
             '  ***'
             )
 
+    # Check for CRLF line endings.
+    with open(os.path.join(topsrcdir, 'configure.py'), 'rb') as fh:
+        data = fh.read()
+        if '\r' in data:
+            die('\n ***\n'
+                ' * The source tree appears to have Windows-style line endings.\n'
+                ' *\n'
+                ' * If using Git, Git is likely configured to use Windows-style\n'
+                ' * line endings.\n'
+                ' *\n'
+                ' * To convert the working copy to UNIX-style line endings, run\n'
+                ' * the following:\n'
+                ' *\n'
+                ' * $ git config core.autocrlf false\n'
+                ' * $ git config core.eof lf\n'
+                ' * $ git rm --cached -r .\n'
+                ' * $ git reset --hard\n'
+                ' *\n'
+                ' * If not using Git, the tool you used to obtain the source\n'
+                ' * code likely converted files to Windows line endings. See\n'
+                ' * usage information for that tool for more.\n'
+                ' ***')
+
     # Check for a couple representative files in the source tree
     conflict_files = [
         '*         %s' % f for f in ('Makefile', 'config/autoconf.mk')
         if exists(os.path.join(topsrcdir, f))
     ]
     if conflict_files:
         die('  ***\n'
             '  *   Your source tree contains these files:\n'
deleted file mode 100644
--- a/build/unix/elfhack/inject/Makefile.in
+++ /dev/null
@@ -1,11 +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/.
-
-include $(topsrcdir)/config/rules.mk
-
-$(CSRCS): %.c: ../inject.c
-	cp $< $@
-
-GARBAGE += $(CSRCS)
new file mode 100644
--- /dev/null
+++ b/build/unix/elfhack/inject/copy_source.py
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+def copy(out_file, in_path):
+    with open(in_path, 'r') as fh:
+        out_file.write(fh.read())
--- a/build/unix/elfhack/inject/moz.build
+++ b/build/unix/elfhack/inject/moz.build
@@ -12,18 +12,26 @@ DIST_INSTALL = False
 
 if CONFIG['TARGET_CPU'].endswith('86'):
     cpu = 'x86'
 elif CONFIG['TARGET_CPU'].startswith('arm'):
     cpu = 'arm'
 else:
     cpu = CONFIG['TARGET_CPU']
 
+gen_src = '%s.c' % cpu
+GENERATED_FILES += [
+    gen_src,
+]
+
+GENERATED_FILES[gen_src].script = 'copy_source.py:copy'
+GENERATED_FILES[gen_src].inputs = ['../inject.c']
+
 SOURCES += [
-    "!%s.c" % cpu,
+    '!%s' % gen_src,
 ]
 
 NO_PGO = True
 
 for v in ('OS_CPPFLAGS', 'OS_CFLAGS', 'DEBUG', 'CLANG_PLUGIN', 'OPTIMIZE',
           'FRAMEPTR'):
     flags = []
     idx = 0
--- a/client.mk
+++ b/client.mk
@@ -26,19 +26,16 @@ define BUILDSTATUS
 @echo 'BUILDSTATUS $1'
 
 endef
 endif
 endif
 
 
 CWD := $(CURDIR)
-ifneq (1,$(words $(CWD)))
-$(error The mozilla directory cannot be located in a path with spaces.)
-endif
 
 ifeq "$(CWD)" "/"
 CWD   := /.
 endif
 
 ifndef TOPSRCDIR
 ifeq (,$(wildcard client.mk))
 TOPSRCDIR := $(patsubst %/,%,$(dir $(MAKEFILE_LIST)))
@@ -54,24 +51,16 @@ PYTHON ?= $(shell which python2.7 > /dev
 CONFIG_GUESS := $(shell $(TOPSRCDIR)/build/autoconf/config.guess)
 
 ####################################
 # Sanity checks
 
 # Windows checks.
 ifneq (,$(findstring mingw,$(CONFIG_GUESS)))
 
-# check for CRLF line endings
-ifneq (0,$(shell $(PERL) -e 'binmode(STDIN); while (<STDIN>) { if (/\r/) { print "1"; exit } } print "0"' < $(TOPSRCDIR)/client.mk))
-$(error This source tree appears to have Windows-style line endings. To \
-convert it to Unix-style line endings, check \
-"https://developer.mozilla.org/en-US/docs/Developer_Guide/Mozilla_build_FAQ\#Win32-specific_questions" \
-for a workaround of this issue.)
-endif
-
 # Set this for baseconfig.mk
 HOST_OS_ARCH=WINNT
 endif
 
 ####################################
 # Load mozconfig Options
 
 # See build pages, http://www.mozilla.org/build/ for how to set up mozconfig.
@@ -81,37 +70,33 @@ define CR
 
 endef
 
 # As $(shell) doesn't preserve newlines, use sed to replace them with an
 # unlikely sequence (||), which is then replaced back to newlines by make
 # before evaluation. $(shell) replacing newlines with spaces, || is always
 # followed by a space (since sed doesn't remove newlines), except on the
 # last line, so replace both '|| ' and '||'.
-# Also, make MOZ_PGO available to mozconfig when passed on make command line.
-MOZCONFIG_CONTENT := $(subst ||,$(CR),$(subst || ,$(CR),$(shell MOZ_PGO=$(MOZ_PGO) $(TOPSRCDIR)/mach environment --format=client.mk | sed 's/$$/||/')))
+MOZCONFIG_CONTENT := $(subst ||,$(CR),$(subst || ,$(CR),$(shell $(TOPSRCDIR)/mach environment --format=client.mk | sed 's/$$/||/')))
 $(eval $(MOZCONFIG_CONTENT))
 
 export FOUND_MOZCONFIG
 
 # As '||' was used as a newline separator, it means it's not occurring in
 # lines themselves. It can thus safely be used to replaces normal spaces,
 # to then replace newlines with normal spaces. This allows to get a list
 # of mozconfig output lines.
 MOZCONFIG_OUT_LINES := $(subst $(CR), ,$(subst $(NULL) $(NULL),||,$(MOZCONFIG_CONTENT)))
 # Filter-out comments from those lines.
 START_COMMENT = \#
 MOZCONFIG_OUT_FILTERED := $(filter-out $(START_COMMENT)%,$(MOZCONFIG_OUT_LINES))
 
 ifdef AUTOCLOBBER
 export AUTOCLOBBER=1
 endif
-ifdef MOZ_PGO
-export MOZ_PGO
-endif
 
 ifdef MOZ_PARALLEL_BUILD
   MOZ_MAKE_FLAGS := $(filter-out -j%,$(MOZ_MAKE_FLAGS))
   MOZ_MAKE_FLAGS += -j$(MOZ_PARALLEL_BUILD)
 endif
 
 # Automatically add -jN to make flags if not defined. N defaults to number of cores.
 ifeq (,$(findstring -j,$(MOZ_MAKE_FLAGS)))
@@ -127,24 +112,23 @@ endif
 
 MOZ_MAKE = $(MAKE) $(MOZ_MAKE_FLAGS) -C $(OBJDIR)
 
 # 'configure' scripts generated by autoconf.
 CONFIGURES := $(TOPSRCDIR)/configure
 CONFIGURES += $(TOPSRCDIR)/js/src/configure
 
 # Make targets that are going to be passed to the real build system
-OBJDIR_TARGETS = install export libs clean realclean distclean maybe_clobber_profiledbuild upload sdk installer package package-compare stage-package source-package l10n-check automation/build
+OBJDIR_TARGETS = install export libs clean realclean distclean upload sdk installer package package-compare stage-package source-package l10n-check automation/build
 
 #######################################################################
 # Rules
 
 # The default rule is build
 build::
-	$(MAKE) -f $(TOPSRCDIR)/client.mk $(if $(MOZ_PGO),profiledbuild,realbuild) CREATE_MOZCONFIG_JSON=
 
 # Include baseconfig.mk for its $(MAKE) validation.
 include $(TOPSRCDIR)/config/baseconfig.mk
 
 # Define mkdir
 include $(TOPSRCDIR)/config/makefiles/makeutils.mk
 include $(TOPSRCDIR)/config/makefiles/autotargets.mk
 
@@ -155,57 +139,30 @@ MOZCONFIG_MK_LINES := $(filter export||%
 	$(if $(MOZCONFIG_MK_LINES),( $(foreach line,$(MOZCONFIG_MK_LINES), echo '$(subst ||, ,$(line))';) )) > $@
 
 # Include that makefile so that it is created. This should not actually change
 # the environment since MOZCONFIG_CONTENT, which MOZCONFIG_OUT_LINES derives
 # from, has already been eval'ed.
 include $(OBJDIR)/.mozconfig.mk
 
 # Print out any options loaded from mozconfig.
-all realbuild clean distclean export libs install realclean::
+all build clean distclean export libs install realclean::
 ifneq (,$(strip $(MOZCONFIG_OUT_FILTERED)))
 	$(info Adding client.mk options from $(FOUND_MOZCONFIG):)
 	$(foreach line,$(MOZCONFIG_OUT_FILTERED),$(info $(NULL) $(NULL) $(NULL) $(NULL) $(subst ||, ,$(line))))
 endif
 
-# Windows equivalents
-build_all: build
-clobber clobber_all: clean
-
 # helper target for mobile
 build_and_deploy: build package install
 
-####################################
-# Profile-Guided Optimization
-#  This is up here so that this is usable in multi-pass builds, where you
-# might not have a runnable application until all the build passes have run.
-profiledbuild::
-	$(call BUILDSTATUS,TIERS pgo_profile_generate pgo_package pgo_profile pgo_clobber pgo_profile_use)
-	$(call BUILDSTATUS,TIER_START pgo_profile_generate)
-	$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_GENERATE=1 MOZ_PGO_INSTRUMENTED=1 CREATE_MOZCONFIG_JSON=
-	$(call BUILDSTATUS,TIER_FINISH pgo_profile_generate)
-	$(call BUILDSTATUS,TIER_START pgo_package)
-	$(MAKE) -C $(OBJDIR) package MOZ_PGO_INSTRUMENTED=1 MOZ_INTERNAL_SIGNING_FORMAT= MOZ_EXTERNAL_SIGNING_FORMAT=
-	rm -f $(OBJDIR)/jarlog/en-US.log
-	$(call BUILDSTATUS,TIER_FINISH pgo_package)
-	$(call BUILDSTATUS,TIER_START pgo_profile)
-	MOZ_PGO_INSTRUMENTED=1 JARLOG_FILE=jarlog/en-US.log EXTRA_TEST_ARGS=10 $(MAKE) -C $(OBJDIR) pgo-profile-run
-	$(call BUILDSTATUS,TIER_FINISH pgo_profile)
-	$(call BUILDSTATUS,TIER_START pgo_clobber)
-	$(MAKE) -f $(TOPSRCDIR)/client.mk maybe_clobber_profiledbuild CREATE_MOZCONFIG_JSON=
-	$(call BUILDSTATUS,TIER_FINISH pgo_clobber)
-	$(call BUILDSTATUS,TIER_START pgo_profile_use)
-	$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_USE=1 CREATE_MOZCONFIG_JSON=
-	$(call BUILDSTATUS,TIER_FINISH pgo_profile_use)
-
 #####################################################
 # Preflight, before building any project
 
 ifdef MOZ_PREFLIGHT_ALL
-realbuild preflight_all::
+build preflight_all::
 	set -e; \
 	for mkfile in $(MOZ_PREFLIGHT_ALL); do \
 	  $(MAKE) -f $(TOPSRCDIR)/$$mkfile preflight_all TOPSRCDIR=$(TOPSRCDIR) OBJDIR=$(OBJDIR) MOZ_OBJDIR=$(MOZ_OBJDIR); \
 	done
 endif
 
 ####################################
 # Configure
@@ -308,53 +265,48 @@ endif
 ifneq (,$(CONFIG_STATUS))
 $(OBJDIR)/config/autoconf.mk: $(TOPSRCDIR)/config/autoconf.mk.in
 	$(PYTHON) $(OBJDIR)/config.status -n --file=$(OBJDIR)/config/autoconf.mk
 endif
 
 ####################################
 # Build it
 
-realbuild::  $(OBJDIR)/Makefile $(OBJDIR)/config.status
+build::  $(OBJDIR)/Makefile $(OBJDIR)/config.status
 	+$(MOZ_MAKE)
 
 ####################################
 # Other targets
 
 # Pass these target onto the real build system
 $(OBJDIR_TARGETS):: $(OBJDIR)/Makefile $(OBJDIR)/config.status
 	+$(MOZ_MAKE) $@
 
 ####################################
 # Postflight, after building all projects
 
 ifdef MOZ_AUTOMATION
-$(if $(MOZ_PGO),profiledbuild,realbuild)::
+build::
 	$(MAKE) -f $(TOPSRCDIR)/client.mk automation/build
 endif
 
 ifdef MOZ_POSTFLIGHT_ALL
-realbuild postflight_all::
+build postflight_all::
 	set -e; \
 	for mkfile in $(MOZ_POSTFLIGHT_ALL); do \
 	  $(MAKE) -f $(TOPSRCDIR)/$$mkfile postflight_all TOPSRCDIR=$(TOPSRCDIR) OBJDIR=$(OBJDIR) MOZ_OBJDIR=$(MOZ_OBJDIR); \
 	done
 endif
 
 echo-variable-%:
 	@echo $($*)
 
 # This makefile doesn't support parallel execution. It does pass
 # MOZ_MAKE_FLAGS to sub-make processes, so they will correctly execute
 # in parallel.
 .NOTPARALLEL:
 
 .PHONY: \
-    realbuild \
     build \
-    profiledbuild \
-    build_all \
-    clobber \
-    clobber_all \
     configure \
     preflight_all \
     postflight_all \
     $(OBJDIR_TARGETS)
--- a/config/makefiles/debugmake.mk
+++ b/config/makefiles/debugmake.mk
@@ -82,17 +82,16 @@ showbuild:
 		COMPILE_CXXFLAGS \
 		COMPILE_CMFLAGS \
 		COMPILE_CMMFLAGS \
 		LDFLAGS \
 		OS_LDFLAGS \
 		DSO_LDOPTS \
 		OS_INCLUDES \
 		OS_LIBS \
-		EXTRA_LIBS \
 		BIN_FLAGS \
 		INCLUDES \
 		DEFINES \
 		ACDEFINES \
 		BIN_SUFFIX \
 		LIB_SUFFIX \
 		RUST_LIB_SUFFIX \
 		DLL_SUFFIX \
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -235,17 +235,17 @@ HOST_PROGRAM :=
 HOST_SIMPLE_PROGRAMS :=
 endif
 
 ALL_TRASH = \
 	$(GARBAGE) $(TARGETS) $(OBJS) $(PROGOBJS) LOGS TAGS a.out \
 	$(filter-out $(ASFILES),$(OBJS:.$(OBJ_SUFFIX)=.s)) $(OBJS:.$(OBJ_SUFFIX)=.ii) \
 	$(OBJS:.$(OBJ_SUFFIX)=.i) $(OBJS:.$(OBJ_SUFFIX)=.i_o) \
 	$(HOST_PROGOBJS) $(HOST_OBJS) $(IMPORT_LIBRARY) \
-	$(EXE_DEF_FILE) so_locations _gen _stubs $(wildcard *.res) $(wildcard *.RES) \
+	so_locations _gen _stubs $(wildcard *.res) $(wildcard *.RES) \
 	$(wildcard *.pdb) $(CODFILE) $(IMPORT_LIBRARY) \
 	$(SHARED_LIBRARY:$(DLL_SUFFIX)=.exp) $(wildcard *.ilk) \
 	$(PROGRAM:$(BIN_SUFFIX)=.exp) $(SIMPLE_PROGRAMS:$(BIN_SUFFIX)=.exp) \
 	$(PROGRAM:$(BIN_SUFFIX)=.lib) $(SIMPLE_PROGRAMS:$(BIN_SUFFIX)=.lib) \
 	$(SIMPLE_PROGRAMS:$(BIN_SUFFIX)=.$(OBJ_SUFFIX)) \
 	$(wildcard gts_tmp_*) $(LIBRARY:%.a=.%.timestamp)
 ALL_TRASH_DIRS = \
 	$(GARBAGE_DIRS) /no-such-file
@@ -334,26 +334,16 @@ ifeq ($(OS_ARCH),NetBSD)
 ifneq (,$(filter arc cobalt hpcmips mipsco newsmips pmax sgimips,$(OS_TEST)))
 ifneq (,$(filter layout/%,$(relativesrcdir)))
 OS_CFLAGS += -Wa,-xgot
 OS_CXXFLAGS += -Wa,-xgot
 endif
 endif
 endif
 
-#
-# Linux: add -Bsymbolic flag for components
-#
-ifeq ($(OS_ARCH),Linux)
-ifdef LD_VERSION_SCRIPT
-EXTRA_DSO_LDOPTS += -Wl,--version-script,$(LD_VERSION_SCRIPT)
-EXTRA_DEPS += $(LD_VERSION_SCRIPT)
-endif
-endif
-
 ifdef SYMBOLS_FILE
 ifeq ($(OS_TARGET),WINNT)
 ifndef GNU_CC
 EXTRA_DSO_LDOPTS += -DEF:$(call normalizepath,$(SYMBOLS_FILE))
 else
 EXTRA_DSO_LDOPTS += $(call normalizepath,$(SYMBOLS_FILE))
 endif
 else
@@ -555,21 +545,21 @@ alltags:
 define EXPAND_CC_OR_CXX
 $(if $(PROG_IS_C_ONLY_$(1)),$(EXPAND_CC),$(EXPAND_CCC))
 endef
 
 #
 # PROGRAM = Foo
 # creates OBJS, links with LIBS to create Foo
 #
-$(PROGRAM): $(PROGOBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(EXE_DEF_FILE) $(RESFILE) $(GLOBAL_DEPS)
+$(PROGRAM): $(PROGOBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(RESFILE) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 	@$(RM) $@.manifest
 ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
-	$(EXPAND_LINK) -NOLOGO -OUT:$@ -PDB:$(LINK_PDBFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(PROGOBJS) $(RESFILE) $(STATIC_LIBS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS)
+	$(EXPAND_LINK) -NOLOGO -OUT:$@ -PDB:$(LINK_PDBFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(PROGOBJS) $(RESFILE) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$@.manifest'; then \
 			echo 'Embedding manifest from $(srcdir)/$@.manifest and $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST '$(win_srcdir)/$@.manifest' $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		else \
 			echo 'Embedding manifest from $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
@@ -580,17 +570,17 @@ ifdef MSMANIFEST_TOOL
 	fi
 endif	# MSVC with manifest tool
 ifdef MOZ_PROFILE_GENERATE
 # touch it a few seconds into the future to work around FAT's
 # 2-second granularity
 	touch -t `date +%Y%m%d%H%M.%S -d 'now+5seconds'` pgo.relink
 endif
 else # !WINNT || GNU_CC
-	$(call EXPAND_CC_OR_CXX,$@) -o $@ $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) $(PROGOBJS) $(RESFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS) $(BIN_FLAGS) $(EXE_DEF_FILE)
+	$(call EXPAND_CC_OR_CXX,$@) -o $@ $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) $(PROGOBJS) $(RESFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
 	$(call CHECK_BINARY,$@)
 endif # WINNT && !GNU_CC
 
 ifdef ENABLE_STRIP
 	$(STRIP) $(STRIP_FLAGS) $@
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
@@ -631,25 +621,25 @@ endif
 # Foo.o (from either Foo.c or Foo.cpp).
 #
 # SIMPLE_PROGRAMS = Foo Bar
 # creates Foo.o Bar.o, links with LIBS to create Foo, Bar.
 #
 $(SIMPLE_PROGRAMS): %$(BIN_SUFFIX): %.$(OBJ_SUFFIX) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
-	$(EXPAND_LINK) -nologo -out:$@ -pdb:$(LINK_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(STATIC_LIBS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS)
+	$(EXPAND_LINK) -nologo -out:$@ -pdb:$(LINK_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		rm -f $@.manifest; \
 	fi
 endif	# MSVC with manifest tool
 else
-	$(call EXPAND_CC_OR_CXX,$@) $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) -o $@ $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS) $(BIN_FLAGS)
+	$(call EXPAND_CC_OR_CXX,$@) $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) -o $@ $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
 	$(call CHECK_BINARY,$@)
 endif # WINNT && !GNU_CC
 
 ifdef ENABLE_STRIP
 	$(STRIP) $(STRIP_FLAGS) $@
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
@@ -665,27 +655,27 @@ ifneq (,$(HOST_CPPSRCS)$(USE_HOST_CXX))
 else
 	$(EXPAND_LIBS_EXEC) -- $(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_CFLAGS) $(INCLUDES) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 endif
 ifndef CROSS_COMPILE
 	$(call CHECK_STDCXX,$@)
 endif
 
-$(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS)) $(EXTRA_DEPS) $(GLOBAL_DEPS)
+$(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 # Always remove both library and library descriptor
 	$(RM) $(REAL_LIBRARY) $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
-	$(EXPAND_AR) $(AR_FLAGS) $(OBJS) $(STATIC_LIBS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS))
+	$(EXPAND_AR) $(AR_FLAGS) $(OBJS) $(STATIC_LIBS)
 
-$(filter-out %.$(LIB_SUFFIX),$(LIBRARY)): $(filter %.$(LIB_SUFFIX),$(LIBRARY)) $(OBJS) $(STATIC_LIBS_DEPS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS)) $(EXTRA_DEPS) $(GLOBAL_DEPS)
+$(filter-out %.$(LIB_SUFFIX),$(LIBRARY)): $(filter %.$(LIB_SUFFIX),$(LIBRARY)) $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 # When we only build a library descriptor, blow out any existing library
 	$(REPORT_BUILD)
 	$(if $(filter %.$(LIB_SUFFIX),$(LIBRARY)),,$(RM) $(REAL_LIBRARY))
-	$(EXPAND_LIBS_GEN) -o $@ $(OBJS) $(STATIC_LIBS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS))
+	$(EXPAND_LIBS_GEN) -o $@ $(OBJS) $(STATIC_LIBS)
 
 ifeq ($(OS_ARCH),WINNT)
 # Import libraries are created by the rules creating shared libraries.
 # The rules to copy them to $(DIST)/lib depend on $(IMPORT_LIBRARY),
 # but make will happily consider the import library before it is refreshed
 # when rebuilding the corresponding shared library. Defining an empty recipe
 # for import libraries forces make to wait for the shared library recipe to
 # have run before considering other targets that depend on the import library.
@@ -703,17 +693,17 @@ endif
 # symlinks back to the originals. The symlinks are a no-op for stabs debugging,
 # so no need to conditionalize on OS version or debugging format.
 
 $(SHARED_LIBRARY): $(OBJS) $(RESFILE) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifndef INCREMENTAL_LINKER
 	$(RM) $@
 endif
-	$(EXPAND_MKSHLIB) $(SHLIB_LDSTARTFILE) $(OBJS) $(SUB_SHLOBJS) $(RESFILE) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(EXTRA_LIBS) $(OS_LIBS) $(SHLIB_LDENDFILE)
+	$(EXPAND_MKSHLIB) $(OBJS) $(RESFILE) $(LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(OS_LIBS)
 	$(call CHECK_BINARY,$@)
 
 ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
 ifdef MSMANIFEST_TOOL
 ifdef EMBED_MANIFEST_AT
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$@.manifest'; then \
 			echo 'Embedding manifest from $(srcdir)/$@.manifest and $@.manifest'; \
--- a/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js
@@ -5,17 +5,17 @@
 
 /**
  * Make sure eval scripts appear in the source list
  */
 
 const ADDON_PATH = "addon-webext-contentscript.xpi";
 const TAB_URL = EXAMPLE_URL + "doc_script_webext_contentscript.html";
 
-let {getExtensionUUID} = Cu.import("resource://gre/modules/Extension.jsm", {});
+const {WebExtensionPolicy} = Cu.getGlobalForObject(Cu.import("resource://gre/modules/Extension.jsm", {}));
 
 function test() {
   let gPanel, gDebugger;
   let gSources, gAddon;
 
   let cleanup = function* (e) {
     if (gAddon) {
       // Remove the addon, if any.
@@ -27,20 +27,20 @@ function test() {
     } else {
       // If no debugger panel was opened, call finish directly.
       finish();
     }
   };
 
   return Task.spawn(function* () {
     gAddon = yield addTemporaryAddon(ADDON_PATH);
-    let uuid = getExtensionUUID(gAddon.id);
+    let {mozExtensionHostname} = WebExtensionPolicy.getByID(gAddon.id);
 
     let options = {
-      source: `moz-extension://${uuid}/webext-content-script.js`,
+      source: `moz-extension://${mozExtensionHostname}/webext-content-script.js`,
       line: 1
     };
     [,, gPanel] = yield initDebugger(TAB_URL, options);
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     is(gSources.values.length, 2, "Should have 2 sources");
 
--- a/devtools/shim/aboutdevtools/aboutdevtools.css
+++ b/devtools/shim/aboutdevtools/aboutdevtools.css
@@ -1,61 +1,68 @@
+/* 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/. */
+
 :root {
   /* Photon color variables used on the aboutdevtools page */
   --blue-60: #0060df;
   --blue-70: #003eaa;
   --blue-80: #002275;
   --grey-30: #d7d7db;
   --grey-90: #0c0c0d;
   --grey-90-alpha-10: rgba(12, 12, 13, 0.1);
   --grey-90-alpha-20: rgba(12, 12, 13, 0.2);
   --grey-90-alpha-30: rgba(12, 12, 13, 0.3);
+  --grey-90-alpha-40: rgba(12, 12, 13, 0.4);
+  --grey-90-alpha-50: rgba(12, 12, 13, 0.5);
+  --teal-60: #00c8d7;
+  --red-50: #ff0039;
   --white: #ffffff;
+
+  /* Shared variables */
+  --line-height: 1.5em;
 }
 
 html, body {
   min-width: 600px;
   height: 100%;
 }
 
 p {
-  line-height: 1.5em;
+  line-height: var(--line-height);
 }
 
 .box {
   width: 100%;
   max-width: 850px;
   display: flex;
-  align-items: center;
-  height: 400px;
   flex-shrink: 0;
+  padding: 34px 0 50px 0;
 }
 
 .wrapper {
   width: 100%;
   height: 100%;
   display: flex;
   flex-direction: column;
   align-items: center;
 }
 
 .left-pane {
-  width: 360px;
-  height: 100%;
+  width: 300px;
+  height: 300px;
+  margin-inline-end: 20px;
   background-image: url(images/otter.svg);
-  background-size: 80%;
+  background-size: 100%;
   background-position: 50%;
   background-repeat: no-repeat;
   flex-shrink: 0;
 }
 
-.right-pane {
-  height: 250px;
-}
-
 .features {
   max-width: 980px;
   border-top: 1px solid var(--grey-30);
 }
 
 .features-list {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
@@ -124,17 +131,17 @@ p {
   display: flex;
 }
 
 .buttons-container button:not(:last-child) {
   margin-right: 10px;
 }
 
 button {
-  margin: 2em 0 0 0;
+  margin: 20px 0 0 0;
   padding: 10px 20px;
 
   border: none;
   border-radius: 2px;
 
   font-size: 15px;
   font-weight: 400;
   line-height: 21px;
--- a/devtools/shim/aboutdevtools/aboutdevtools.xhtml
+++ b/devtools/shim/aboutdevtools/aboutdevtools.xhtml
@@ -9,17 +9,19 @@
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
 <head>
   <title>&aboutDevtools.headTitle;</title>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>a
   <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools-shim/content/aboutdevtools/aboutdevtools.css"  type="text/css"/>
+  <link rel="stylesheet" href="chrome://devtools-shim/content/aboutdevtools/subscribe.css"  type="text/css"/>
   <script type="application/javascript" src="chrome://devtools-shim/content/aboutdevtools/aboutdevtools.js"></script>
+  <script type="application/javascript" src="chrome://devtools-shim/content/aboutdevtools/subscribe.js"></script>
 </head>
 <body>
   <div id="install-page" class="wrapper" hidden="true">
     <div class="box">
       <div class="left-pane" />
       <div class="right-pane">
         <h1 class="title" id="common-title" hidden="true">&aboutDevtools.enable.title;</h1>
         <h1 class="title" id="inspect-title" hidden="true">&aboutDevtools.enable.inspectElementTitle;</h1>
@@ -43,16 +45,43 @@
 
   <!-- This page, hidden by default is displayed once the add-on is installed -->
   <div id="welcome-page" class="wrapper" hidden="true">
     <div class="box">
       <div class="left-pane" />
       <div class="right-pane">
         <h1 class="title" >&aboutDevtools.welcome.title;</h1>
         <p id="welcome-message">&aboutDevtools.welcome.message;</p>
+
+        <!-- Form dedicated to the newsletter subscription -->
+        <div class="newsletter">
+          <h2 class="newsletter-title">&aboutDevtools.newsletter.title;</h2>
+          <p>&aboutDevtools.newsletter.message;</p>
+
+          <form id="newsletter-form" name="newsletter-form" action="https://www.mozilla.org/en-US/newsletter/" method="post">
+            <!-- "H" stands for the HTML format (->fmt). Alternative is T for text. -->
+            <input type="hidden" id="fmt" name="fmt" value="H" />
+            <!-- "app-dev" is the id of the Mozilla Developper newsletter -->
+            <input type="hidden" id="newsletters" name="newsletters" value="app-dev" />
+            <div id="newsletter-errors"></div>
+            <section id="newsletter-email" class="newsletter-form-section">
+              <input type="email" id="email" name="email" required="true" placeholder="&aboutDevtools.newsletter.email.placeholder;" />
+            </section>
+
+            <section id="newsletter-privacy" class="newsletter-form-section">
+              <input type="checkbox" id="privacy" name="privacy" required="true" />
+              <label for="privacy">&aboutDevtools.newsletter.privacy.label;</label>
+            </section>
+            <button type="submit" id="newsletter-submit" class="primary-button">&aboutDevtools.newsletter.subscribe.label;</button>
+          </form>
+          <div id="newsletter-thanks">
+            <h2>&aboutDevtools.newsletter.thanks.title;</h2>
+            <p>&aboutDevtools.newsletter.thanks.message;</p>
+          </div>
+        </div>
       </div>
     </div>
 
     <div class="features">
       <ul class="features-list">
       </ul>
     </div>
 
new file mode 100644
--- /dev/null
+++ b/devtools/shim/aboutdevtools/subscribe.css
@@ -0,0 +1,100 @@
+/* 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/. */
+
+/**
+ * This file contains the styles for the newsletter subscription form on about:devtools.
+ * It is largely inspired from https://mozilla.github.io/basket-example/
+ */
+
+#newsletter-errors {
+  /* Hidden by default */
+  display: none;
+
+  margin-bottom: 20px;
+  padding: 10px;
+  border-radius: 2px;
+
+  background-color: var(--red-50);
+  color: var(--white);
+}
+
+#newsletter-errors.show {
+  display: block;
+}
+
+#newsletter-errors .error {
+  margin: 0;
+  margin-bottom: 10px;
+}
+
+#newsletter-errors .error:last-child {
+  margin-bottom: 0;
+}
+
+#newsletter-thanks {
+  /* Hidden by default */
+  display: none;
+}
+
+#newsletter-thanks.show {
+  display: block;
+}
+
+.newsletter-form-section {
+  display: block;
+  margin-bottom: 20px;
+  width: 320px;
+}
+
+#newsletter-privacy {
+  display: flex;
+
+  /* The privacy section is hidden by default and only displayed on focus */
+  height: 0;
+  margin-bottom: -20px;
+  overflow: hidden;
+}
+
+#newsletter-privacy.animate {
+  transition: all 0.25s cubic-bezier(.15,.75,.35,.9);
+}
+
+#newsletter-privacy label {
+  line-height: var(--line-height);
+}
+
+#privacy {
+  width: 20px;
+  height: 20px;
+  margin: 2px;
+  margin-inline-end: 10px;
+  flex-shrink: 0;
+}
+
+#email {
+  width: 100%;
+  box-sizing: border-box;
+  padding: 12px 15px;
+}
+
+#newsletter-form input {
+  border-color: var(--grey-90-alpha-30);
+}
+
+#newsletter-form input:hover {
+  border-color: var(--grey-90-alpha-50);
+}
+
+#newsletter-form input:focus {
+  border-color: var(--teal-60);
+  box-shadow: 0 0 2px 0 var(--teal-60);
+}
+
+#newsletter-form::placeholder {
+  color: var(--grey-90-alpha-40);
+}
+
+#newsletter-submit {
+  display: block;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shim/aboutdevtools/subscribe.js
@@ -0,0 +1,147 @@
+/* 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 handles the newsletter subscription form on about:devtools.
+ * It is largely inspired from https://mozilla.github.io/basket-example/
+ */
+
+window.addEventListener("load", function () {
+  const { utils: Cu } = Components;
+  const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+  // Timeout for the subscribe XHR.
+  const REQUEST_TIMEOUT = 5000;
+
+  const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties";
+  const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS);
+
+  let emailInput = document.getElementById("email");
+  let newsletterErrors = document.getElementById("newsletter-errors");
+  let newsletterForm = document.getElementById("newsletter-form");
+  let newsletterPrivacySection = document.getElementById("newsletter-privacy");
+  let newsletterThanks = document.getElementById("newsletter-thanks");
+
+  /**
+   * Update the error panel to display the provided errors. If the argument is null or
+   * empty, a default error message will be displayed.
+   *
+   * @param {Array} errors
+   *        Array of strings, each item being an error message to display.
+   */
+  function updateErrorPanel(errors) {
+    clearErrorPanel();
+
+    if (!errors || errors.length == 0) {
+      errors = [aboutDevtoolsBundle.GetStringFromName("newsletter.error.unknown")];
+    }
+
+    // Create errors markup.
+    let fragment = document.createDocumentFragment();
+    for (let error of errors) {
+      let item = document.createElement("p");
+      item.classList.add("error");
+      item.appendChild(document.createTextNode(error));
+      fragment.appendChild(item);
+    }
+
+    newsletterErrors.appendChild(fragment);
+    newsletterErrors.classList.add("show");
+  }
+
+  /**
+   * Hide the error panel and remove all errors.
+   */
+  function clearErrorPanel() {
+    newsletterErrors.classList.remove("show");
+    newsletterErrors.innerHTML = "";
+  }
+
+  // Show the additional form fields on focus of the email input.
+  function onEmailInputFocus() {
+    // Create a hidden measuring container, append it to the parent of the privacy section
+    let container = document.createElement("div");
+    container.style.cssText = "visibility: hidden; overflow: hidden; position: absolute";
+    newsletterPrivacySection.parentNode.appendChild(container);
+
+    // Clone the privacy section, append the clone to the measuring container.
+    let clone = newsletterPrivacySection.cloneNode(true);
+    container.appendChild(clone);
+
+    // Measure the target height of the privacy section.
+    clone.style.height = "auto";
+    let height = clone.offsetHeight;
+
+    // Cleanup the measuring container.
+    container.remove();
+
+    // Set the animate class and set the height to the measured height.
+    newsletterPrivacySection.classList.add("animate");
+    newsletterPrivacySection.style.cssText = `height: ${height}px; margin-bottom: 0;`;
+  }
+
+  // XHR subscribe; handle errors; display thanks message on success.
+  function onFormSubmit(evt) {
+    evt.preventDefault();
+    evt.stopPropagation();
+
+    // New submission, clear old errors
+    clearErrorPanel();
+
+    let xhr = new XMLHttpRequest();
+
+    xhr.onload = function (r) {
+      if (r.target.status >= 200 && r.target.status < 300) {
+        let {response} = r.target;
+
+        if (response.success === true) {
+          // Hide form and show success message.
+          newsletterForm.style.display = "none";
+          newsletterThanks.classList.add("show");
+        } else {
+          // We trust the error messages from the service to be meaningful for the user.
+          updateErrorPanel(response.errors);
+        }
+      } else {
+        let {status, statusText} = r.target;
+        let statusInfo = `${status} - ${statusText}`;
+        let error = aboutDevtoolsBundle
+          .formatStringFromName("newsletter.error.common", [statusInfo], 1);
+        updateErrorPanel([error]);
+      }
+    };
+
+    xhr.onerror = () => {
+      updateErrorPanel();
+    };
+
+    xhr.ontimeout = () => {
+      let error = aboutDevtoolsBundle.GetStringFromName("newsletter.error.timeout");
+      updateErrorPanel([error]);
+    };
+
+    let url = newsletterForm.getAttribute("action");
+
+    xhr.open("POST", url, true);
+    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+    xhr.timeout = REQUEST_TIMEOUT;
+    xhr.responseType = "json";
+
+    // Create form data.
+    let formData = new FormData(newsletterForm);
+    formData.append("source_url", document.location.href);
+
+    let params = new URLSearchParams(formData);
+
+    // Send the request.
+    xhr.send(params.toString());
+  }
+
+  // Attach event listeners.
+  newsletterForm.addEventListener("submit", onFormSubmit);
+  emailInput.addEventListener("focus", onEmailInputFocus);
+}, { once: true });
--- a/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd
+++ b/devtools/shim/aboutdevtools/tmp-locale/aboutdevtools.dtd
@@ -17,9 +17,19 @@
 
 <!ENTITY  aboutDevtools.enable.commonMessage
           "As of Firefox 58, Developer Tools are disabled by default to give you more control over your browser.">
 
 <!ENTITY  aboutDevtools.enable.learnMoreLink "Learn more about DevTools">
 <!ENTITY  aboutDevtools.enable.installButton "Enable Developer Tools">
 <!ENTITY  aboutDevtools.enable.closeButton "Close this page">
 <!ENTITY  aboutDevtools.welcome.title "Welcome to Firefox Developer Tools!">
-<!ENTITY  aboutDevtools.welcome.message "You’ve successfully enabled DevTools! To get started, explore the Web Developer menu or open the tools with ##INSPECTOR_SHORTCUT##.">
\ No newline at end of file
+<!ENTITY  aboutDevtools.welcome.message "You’ve successfully enabled DevTools! To get started, explore the Web Developer menu or open the tools with ##INSPECTOR_SHORTCUT##.">
+
+<!ENTITY  aboutDevtools.newsletter.title "Mozilla Developer Newsletter">
+<!ENTITY  aboutDevtools.newsletter.message "Get developer news, tricks and resources sent straight to your inbox.">
+<!ENTITY  aboutDevtools.newsletter.email.placeholder "Email">
+<!ENTITY  aboutDevtools.newsletter.privacy.label "I’m okay with Mozilla handling my info as explained in this <a class=&#x0022;external&#x0022; href=&#x0022;https://www.mozilla.org/privacy/&#x0022;>Privacy Policy</a>.">
+<!ENTITY  aboutDevtools.newsletter.subscribe.label "Subscribe">
+
+<!ENTITY  aboutDevtools.newsletter.thanks.title "Thanks!">
+<!ENTITY  aboutDevtools.newsletter.thanks.message "If you haven’t previously confirmed a subscription to a Mozilla-related newsletter you may have to do so. Please check your inbox or your spam filter for an email from us.">
+
--- a/devtools/shim/jar.mn
+++ b/devtools/shim/jar.mn
@@ -2,16 +2,18 @@
 # 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/.
 
 devtools-shim.jar:
 %   content devtools-shim %content/
     content/aboutdevtools/aboutdevtools.xhtml  (aboutdevtools/aboutdevtools.xhtml)
     content/aboutdevtools/aboutdevtools.css (aboutdevtools/aboutdevtools.css)
     content/aboutdevtools/aboutdevtools.js (aboutdevtools/aboutdevtools.js)
+    content/aboutdevtools/subscribe.css (aboutdevtools/subscribe.css)
+    content/aboutdevtools/subscribe.js (aboutdevtools/subscribe.js)
 
     content/aboutdevtools/images/otter.svg (aboutdevtools/images/otter.svg)
 
     # Temporary localisation file, move back to devtools/shim/locales/en-US when ready for localization
     # See https://bugzilla.mozilla.org/show_bug.cgi?id=1408369
     content/aboutdevtools/tmp-locale/aboutdevtools.dtd (aboutdevtools/tmp-locale/aboutdevtools.dtd)
 
     content/aboutdevtools/images/dev-edition-logo.svg (aboutdevtools/images/dev-edition-logo.svg)
--- a/devtools/shim/locales/en-US/aboutdevtools.properties
+++ b/devtools/shim/locales/en-US/aboutdevtools.properties
@@ -30,8 +30,21 @@ features.responsive.desc=Test sites on e
 features.visualediting.title=Visual Editing
 features.visualediting.desc=Fine-tune animations, alignment and padding.
 
 features.performance.title=Performance
 features.performance.desc=Unblock bottlenecks, streamline processes, optimize assets.
 
 features.memory.title=Memory
 features.memory.desc=Find memory leaks and make your application zippy.
+
+# LOCALIZATION NOTE (newsletter.error.common): error text displayed when the newsletter
+# subscription failed. The argument will be replaced with request's status code and status
+# text (e.g. "404 - Not Found")
+newsletter.error.common=Subscription request failed (%S).
+
+# LOCALIZATION NOTE (newsletter.error.unknown): error text displayed when the newsletter
+# subscription failed for an unexpected reason.
+newsletter.error.unknown=An unexpected error occurred.
+
+# LOCALIZATION NOTE (newsletter.error.timeout): error text displayed when the newsletter
+# subscription timed out.
+newsletter.error.timeout=Subscription request timed out.
--- a/dom/animation/test/css-animations/file_event-dispatch.html
+++ b/dom/animation/test/css-animations/file_event-dispatch.html
@@ -29,351 +29,348 @@ function AnimationEventHandler(target) {
   };
   this.target.onanimationend = evt => {
     this.animationend = evt.elapsedTime;
   };
   this.target.onanimationcancel = evt => {
     this.animationcancel = evt.elapsedTime;
   };
 }
-AnimationEventHandler.prototype.clear = function() {
+AnimationEventHandler.prototype.clear = () => {
   this.animationstart     = undefined;
   this.animationiteration = undefined;
   this.animationend       = undefined;
   this.animationcancel    = undefined;
 }
 
 function setupAnimation(t, animationStyle) {
-  var div = addDiv(t, { style: "animation: " + animationStyle });
-  var watcher = new EventWatcher(t, div, [ 'animationstart',
+  const div = addDiv(t, { style: 'animation: ' + animationStyle });
+  // Note that this AnimationEventHandler should be created before EventWatcher
+  // to capture all events in the handler prior to the EventWatcher since
+  // testharness.js proceeds when the EventWatcher received watching events.
+  const handler = new AnimationEventHandler(div);
+  const watcher = new EventWatcher(t, div, [ 'animationstart',
                                            'animationiteration',
                                            'animationend',
                                            'animationcancel' ]);
-  var animation = div.getAnimations()[0];
+  const animation = div.getAnimations()[0];
 
-  return [animation, watcher, div];
+  return { animation, watcher, div, handler };
 }
 
-promise_test(function(t) {
+promise_test(t => {
   // Add 1ms delay to ensure that the delay is not included in the elapsedTime.
-  const [animation, watcher] = setupAnimation(t, 'anim 100s 1ms');
+  const { animation, watcher } = setupAnimation(t, 'anim 100s 1ms');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Idle -> Active');
 
-promise_test(function(t) {
-  const [animation, watcher, div] = setupAnimation(t, 'anim 100s');
-  const handler = new AnimationEventHandler(div);
+promise_test(t => {
+  const { animation, watcher, div, handler } = setupAnimation(t, 'anim 100s');
 
   // Seek to After phase.
   animation.finish();
   return watcher.wait_for([ 'animationstart',
-                            'animationend' ]).then(function() {
+                            'animationend' ]).then(() => {
     assert_equals(handler.animationstart, 0.0);
     assert_equals(handler.animationend, 100);
   });
 }, 'Idle -> After');
 
-promise_test(function(t) {
-  const [animation, watcher] =
+promise_test(t => {
+  const { animation, watcher } =
     setupAnimation(t, 'anim 100s 100s paused');
 
-  return animation.ready.then(function() {
+  return animation.ready.then(() => {
     // Seek to Active phase.
     animation.currentTime = 100 * MS_PER_SEC;
     return watcher.wait_for('animationstart');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Before -> Active');
 
-promise_test(function(t) {
-  const [animation, watcher, div] =
+promise_test(t => {
+  const { animation, watcher, div, handler } =
     setupAnimation(t, 'anim 100s 100s paused');
-  const handler = new AnimationEventHandler(div);
 
-  return animation.ready.then(function() {
+  return animation.ready.then(() => {
     // Seek to After phase.
     animation.finish();
     return watcher.wait_for([ 'animationstart', 'animationend' ]);
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(handler.animationstart, 0.0);
     assert_equals(handler.animationend, 100.0);
   });
 }, 'Before -> After');
 
-promise_test(function(t) {
-  const [animation, watcher, div] = setupAnimation(t, 'anim 100s paused');
+promise_test(t => {
+  const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Make idle
     div.style.display = 'none';
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Idle, display: none');
 
-promise_test(function(t) {
-  const [animation, watcher, div] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher, div } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     animation.currentTime = 100.0;
     // Make idle
     animation.timeline = null;
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_times_equal(evt.elapsedTime, 0.1);
   });
 }, 'Active -> Idle, setting Animation.timeline = null');
 
-promise_test(function(t) {
+promise_test(t => {
   // we should NOT pause animation since calling cancel synchronously.
-  const [animation, watcher, div] = setupAnimation(t, 'anim 100s');
+  const { animation, watcher, div } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     animation.currentTime = 50.0;
     animation.cancel();
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_times_equal(evt.elapsedTime, 0.05);
   });
 }, 'Active -> Idle, calling Animation.cancel()');
 
-promise_test(function(t) {
-  const [animation, watcher] =
+promise_test(t => {
+  const { animation, watcher } =
     setupAnimation(t, 'anim 100s 100s paused');
 
   // Seek to Active phase.
   animation.currentTime = 100 * MS_PER_SEC;
-  return watcher.wait_for('animationstart').then(function() {
+  return watcher.wait_for('animationstart').then(() => {
     // Seek to Before phase.
     animation.currentTime = 0;
     return watcher.wait_for('animationend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Before');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s paused');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s paused');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Seek to After phase.
     animation.finish();
     return watcher.wait_for('animationend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 100.0);
   });
 }, 'Active -> After');
 
-promise_test(function(t) {
-  const [animation, watcher, div] =
+promise_test(t => {
+  const { animation, watcher, div, handler } =
     setupAnimation(t, 'anim 100s 100s paused');
-  const handler = new AnimationEventHandler(div);
 
   // Seek to After phase.
   animation.finish();
   return watcher.wait_for([ 'animationstart',
-                            'animationend' ]).then(function() {
+                            'animationend' ]).then(() => {
     // Seek to Before phase.
     animation.currentTime = 0;
     handler.clear();
     return watcher.wait_for([ 'animationstart', 'animationend' ]);
-  }).then(function() {
+  }).then(() => {
     assert_equals(handler.animationstart, 100.0);
     assert_equals(handler.animationend, 0.0);
   });
 }, 'After -> Before');
 
-promise_test(function(t) {
-  const [animation, watcher, div] =
+promise_test(t => {
+  const { animation, watcher, div } =
     setupAnimation(t, 'anim 100s 100s paused');
-  const handler = new AnimationEventHandler(div);
 
   // Seek to After phase.
   animation.finish();
   return watcher.wait_for([ 'animationstart',
-                            'animationend' ]).then(function() {
+                            'animationend' ]).then(() => {
     // Seek to Active phase.
     animation.currentTime = 100 * MS_PER_SEC;
-    handler.clear();
     return watcher.wait_for('animationstart');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 100.0);
   });
 }, 'After -> Active');
 
-promise_test(function(t) {
-  const [animation, watcher, div]
+promise_test(t => {
+  const { animation, watcher, div }
     = setupAnimation(t, 'anim 100s 100s 3 paused');
-  const handler = new AnimationEventHandler(div);
 
-  return animation.ready.then(function() {
+  return animation.ready.then(() => {
     // Seek to iteration 0 (no animationiteration event should be dispatched)
     animation.currentTime = 100 * MS_PER_SEC;
     return watcher.wait_for('animationstart');
-  }).then(function(evt) {
+  }).then(evt => {
     // Seek to iteration 2
     animation.currentTime = 300 * MS_PER_SEC;
-    handler.clear();
     return watcher.wait_for('animationiteration');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 200);
     // Seek to After phase (no animationiteration event should be dispatched)
     animation.currentTime = 400 * MS_PER_SEC;
     return watcher.wait_for('animationend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 300);
   });
 }, 'Active -> Active (forwards)');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s 100s 3');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3');
 
   // Seek to After phase.
   animation.finish();
   return watcher.wait_for([ 'animationstart',
-                            'animationend' ]).then(function() {
+                            'animationend' ]).then(() => {
     // Seek to iteration 2 (no animationiteration event should be dispatched)
     animation.pause();
     animation.currentTime = 300 * MS_PER_SEC;
     return watcher.wait_for('animationstart');
-  }).then(function() {
+  }).then(() => {
     // Seek to mid of iteration 0 phase.
     animation.currentTime = 200 * MS_PER_SEC;
     return watcher.wait_for('animationiteration');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 200.0);
     // Seek to before phase (no animationiteration event should be dispatched)
     animation.currentTime = 0;
     return watcher.wait_for('animationend');
   });
 }, 'Active -> Active (backwards)');
 
-promise_test(function(t) {
-  const [animation, watcher, div] =
+promise_test(t => {
+  const { animation, watcher, div } =
     setupAnimation(t, 'anim 100s paused');
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Seek to Idle phase.
     div.style.display = 'none';
     flushComputedStyle(div);
 
     return watcher.wait_for('animationcancel');
-  }).then(function() {
+  }).then(() => {
     // Restart this animation.
     div.style.display = '';
     return watcher.wait_for('animationstart');
   });
 }, 'Active -> Idle -> Active: animationstart is fired by restarting animation');
 
-promise_test(function(t) {
-  const [animation, watcher] =
+promise_test(t => {
+  const { animation, watcher } =
     setupAnimation(t, 'anim 100s 100s 2 paused');
 
   // Make After.
   animation.finish();
   return watcher.wait_for([ 'animationstart',
-                            'animationend' ]).then(function(evt) {
+                            'animationend' ]).then(evt => {
     animation.playbackRate = -1;
     return watcher.wait_for('animationstart');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 200);
     // Seek to 1st iteration
     animation.currentTime = 200 * MS_PER_SEC - 1;
     return watcher.wait_for('animationiteration');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 100);
     // Seek to before
     animation.currentTime = 100 * MS_PER_SEC - 1;
     return watcher.wait_for('animationend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0);
     assert_equals(animation.playState, 'running'); // delay
   });
 }, 'Negative playbackRate sanity test(Before -> Active -> Before)');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Make idle
     animation.cancel();
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     animation.cancel();
     // Then wait a couple of frames and check that no event was dispatched.
     return waitForAnimationFrames(2);
   });
 }, 'Call Animation.cancel after cancelling animation.');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Make idle
     animation.cancel();
     animation.play();
     return watcher.wait_for([ 'animationcancel',
                               'animationstart' ]);
   });
 }, 'Restart animation after cancelling animation immediately.');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Make idle
     animation.cancel();
     animation.play();
     animation.cancel();
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     // Then wait a couple of frames and check that no event was dispatched.
     return waitForAnimationFrames(2);
   });
 }, 'Call Animation.cancel after restarting animation immediately.');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Make idle
     animation.timeline = null;
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     animation.timeline = document.timeline;
     animation.play();
     return watcher.wait_for('animationstart');
   });
 }, 'Set timeline and play transition after clearing the timeline.');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     // Make idle
     animation.cancel();
     return watcher.wait_for('animationcancel');
-  }).then(function(evt) {
+  }).then(evt => {
     animation.effect = null;
     // Then wait a couple of frames and check that no event was dispatched.
     return waitForAnimationFrames(2);
   });
 }, 'Set null target effect after cancelling the animation.');
 
-promise_test(function(t) {
-  const [animation, watcher] = setupAnimation(t, 'anim 100s');
+promise_test(t => {
+  const { animation, watcher } = setupAnimation(t, 'anim 100s');
 
-  return watcher.wait_for('animationstart').then(function(evt) {
+  return watcher.wait_for('animationstart').then(evt => {
     animation.effect = null;
     return watcher.wait_for('animationend');
-  }).then(function(evt) {
+  }).then(evt => {
     animation.cancel();
     // Then wait a couple of frames and check that no event was dispatched.
     return waitForAnimationFrames(2);
   });
 }, 'Cancel the animation after clearing the target effect.');
 
 done();
 </script>
--- a/dom/animation/test/css-transitions/file_event-dispatch.html
+++ b/dom/animation/test/css-transitions/file_event-dispatch.html
@@ -24,450 +24,448 @@ function TransitionEventHandler(target) 
   this.target.ontransitionend = evt => {
     this.transitionend = evt.elapsedTime;
   };
   this.target.ontransitioncancel = evt => {
     this.transitioncancel = evt.elapsedTime;
   };
 }
 
-TransitionEventHandler.prototype.clear = function() {
+TransitionEventHandler.prototype.clear = () => {
   this.transitionrun    = undefined;
   this.transitionstart  = undefined;
   this.transitionend    = undefined;
   this.transitioncancel = undefined;
 };
 
 function setupTransition(t, transitionStyle) {
-  var div = addDiv(t, { style: 'transition: ' + transitionStyle });
-  var watcher = new EventWatcher(t, div, [ 'transitionrun',
+  const div = addDiv(t, { style: 'transition: ' + transitionStyle });
+  // Note that this TransitionEventHandler should be created before EventWatcher
+  // to capture all events in the handler prior to the EventWatcher since
+  // testharness.js proceeds when the EventWatcher received watching events.
+  const handler = new TransitionEventHandler(div);
+  const watcher = new EventWatcher(t, div, [ 'transitionrun',
                                            'transitionstart',
                                            'transitionend',
                                            'transitioncancel' ]);
   flushComputedStyle(div);
 
   div.style.marginLeft = '100px';
-  var transition = div.getAnimations()[0];
+  const transition = div.getAnimations()[0];
 
-  return [transition, watcher, div];
+  return { transition, watcher, div, handler };
 }
 
 // On the next frame (i.e. when events are queued), whether or not the
 // transition is still pending depends on the implementation.
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
-  return watcher.wait_for('transitionrun').then(function(evt) {
+  return watcher.wait_for('transitionrun').then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Idle -> Pending or Before');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
   // Force the transition to leave the idle phase
   transition.startTime = document.timeline.currentTime;
-  return watcher.wait_for('transitionrun').then(function(evt) {
+  return watcher.wait_for('transitionrun').then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Idle -> Before');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div, handler } =
     setupTransition(t, 'margin-left 100s 100s');
-  var handler = new TransitionEventHandler(div);
 
   // Seek to Active phase.
   transition.currentTime = 100 * MS_PER_SEC;
   transition.pause();
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     assert_equals(handler.transitionrun, 0.0);
     assert_equals(handler.transitionstart, 0.0);
   });
 }, 'Idle or Pending -> Active');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div, handler } =
     setupTransition(t, 'margin-left 100s 100s');
-  var handler = new TransitionEventHandler(div);
 
   // Seek to After phase.
   transition.finish();
   return watcher.wait_for([ 'transitionrun',
                             'transitionstart',
-                            'transitionend' ]).then(function(evt) {
+                            'transitionend' ]).then(evt => {
     assert_equals(handler.transitionrun, 0.0);
     assert_equals(handler.transitionstart, 0.0);
     assert_equals(handler.transitionend, 100.0);
   });
 }, 'Idle or Pending -> After');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s 100s');
 
   return Promise.all([ watcher.wait_for('transitionrun'),
-                       transition.ready ]).then(function() {
+                       transition.ready ]).then(() => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Before -> Idle (display: none)');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
 
   return Promise.all([ watcher.wait_for('transitionrun'),
-                       transition.ready ]).then(function() {
+                       transition.ready ]).then(() => {
     // Make idle
     transition.timeline = null;
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Before -> Idle (Animation.timeline = null)');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
 
   return Promise.all([ watcher.wait_for('transitionrun'),
-                       transition.ready ]).then(function() {
+                       transition.ready ]).then(() => {
     transition.currentTime = 100 * MS_PER_SEC;
     return watcher.wait_for('transitionstart');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Before -> Active');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div, handler } =
     setupTransition(t, 'margin-left 100s 100s');
-  var handler = new TransitionEventHandler(div);
 
   return Promise.all([ watcher.wait_for('transitionrun'),
-                       transition.ready ]).then(function() {
+                       transition.ready ]).then(() => {
     // Seek to After phase.
     transition.currentTime = 200 * MS_PER_SEC;
     return watcher.wait_for([ 'transitionstart', 'transitionend' ]);
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(handler.transitionstart, 0.0);
     assert_equals(handler.transitionend, 100.0);
   });
 }, 'Before -> After');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s');
 
   // Seek to Active start position.
   transition.pause();
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Idle, no delay (display: none)');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s');
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     transition.currentTime = 0;
     transition.timeline = null;
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Idle, no delay (Animation.timeline = null)');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s 100s');
   // Pause so the currentTime is fixed and we can accurately compare the event
   // time in transition cancel events.
   transition.pause();
 
   // Seek to Active phase.
   transition.currentTime = 100 * MS_PER_SEC;
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Idle, with positive delay (display: none)');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
 
   // Seek to Active phase.
   transition.currentTime = 100 * MS_PER_SEC;
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     transition.currentTime = 100 * MS_PER_SEC;
     transition.timeline = null;
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Idle, with positive delay (Animation.timeline = null)');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s -50s');
 
   // Pause so the currentTime is fixed and we can accurately compare the event
   // time in transition cancel events.
   transition.pause();
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 50.0);
   });
 }, 'Active -> Idle, with negative delay (display: none)');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s -50s');
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     transition.currentTime = 50 * MS_PER_SEC;
     transition.timeline = null;
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Idle, with negative delay (Animation.timeline = null)');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
   // Seek to Active phase.
   transition.currentTime = 100 * MS_PER_SEC;
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Seek to Before phase.
     transition.currentTime = 0;
     return watcher.wait_for('transitionend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 0.0);
   });
 }, 'Active -> Before');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
   // Seek to Active phase.
   transition.currentTime = 100 * MS_PER_SEC;
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Seek to After phase.
     transition.currentTime = 200 * MS_PER_SEC;
     return watcher.wait_for('transitionend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 100.0);
   });
 }, 'Active -> After');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div, handler } =
     setupTransition(t, 'margin-left 100s 100s');
-  var handler = new TransitionEventHandler(div);
 
   // Seek to After phase.
   transition.finish();
   return watcher.wait_for([ 'transitionrun',
                             'transitionstart',
-                            'transitionend' ]).then(function(evt) {
+                            'transitionend' ]).then(evt => {
     // Seek to Before phase.
     transition.currentTime = 0;
     return watcher.wait_for([ 'transitionstart', 'transitionend' ]);
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(handler.transitionstart, 100.0);
     assert_equals(handler.transitionend, 0.0);
   });
 }, 'After -> Before');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s 100s');
   // Seek to After phase.
   transition.finish();
   return watcher.wait_for([ 'transitionrun',
                             'transitionstart',
-                            'transitionend' ]).then(function(evt) {
+                            'transitionend' ]).then(evt => {
     // Seek to Active phase.
     transition.currentTime = 100 * MS_PER_SEC;
     return watcher.wait_for('transitionstart');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 100.0);
   });
 }, 'After -> Active');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div, handler } =
     setupTransition(t, 'margin-left 100s -50s');
-  var handler = new TransitionEventHandler(div);
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function() {
+                            'transitionstart' ]).then(() => {
     assert_equals(handler.transitionrun, 50.0);
     assert_equals(handler.transitionstart, 50.0);
     transition.finish();
     return watcher.wait_for('transitionend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 100.0);
   });
 }, 'Calculating the interval start and end time with negative start delay.');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div, handler } =
     setupTransition(t, 'margin-left 100s 100s');
-  var handler = new TransitionEventHandler(div);
 
-  return watcher.wait_for('transitionrun').then(function(evt) {
+  return watcher.wait_for('transitionrun').then(evt => {
     // We can't set the end delay via generated effect timing.
     // Because CSS-Transition use the AnimationEffectTimingReadOnly.
     transition.effect = new KeyframeEffect(div,
                                            { marginleft: [ '0px', '100px' ]},
                                            { duration: 100 * MS_PER_SEC,
                                              endDelay: -50 * MS_PER_SEC });
     // Seek to Before and play.
     transition.cancel();
     transition.play();
     return watcher.wait_for([ 'transitioncancel',
                               'transitionrun',
                               'transitionstart' ]);
-  }).then(function() {
+  }).then(() => {
     assert_equals(handler.transitionstart, 0.0);
 
     // Seek to After phase.
     transition.finish();
     return watcher.wait_for('transitionend');
-  }).then(function(evt) {
+  }).then(evt => {
     assert_equals(evt.elapsedTime, 50.0);
   });
 }, 'Calculating the interval start and end time with negative end delay.');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s 100s');
 
-  return watcher.wait_for('transitionrun').then(function() {
+  return watcher.wait_for('transitionrun').then(() => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     return watcher.wait_for('transitioncancel');
-  }).then(function() {
+  }).then(() => {
     transition.cancel();
     // Then wait a couple of frames and check that no event was dispatched
     return waitForAnimationFrames(2);
   });
 }, 'Call Animation.cancel after cancelling transition.');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s 100s');
 
-  return watcher.wait_for('transitionrun').then(function(evt) {
+  return watcher.wait_for('transitionrun').then(evt => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     transition.play();
     watcher.wait_for([ 'transitioncancel',
                        'transitionrun',
                        'transitionstart' ]);
   });
 }, 'Restart transition after cancelling transition immediately');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s 100s');
 
-  return watcher.wait_for('transitionrun').then(function(evt) {
+  return watcher.wait_for('transitionrun').then(evt => {
     // Make idle
     div.style.display = 'none';
     flushComputedStyle(div);
     transition.play();
     transition.cancel();
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     // Then wait a couple of frames and check that no event was dispatched
     return waitForAnimationFrames(2);
   });
 }, 'Call Animation.cancel after restarting transition immediately');
 
-promise_test(function(t) {
-  var [transition, watcher] =
+promise_test(t => {
+  const { transition, watcher } =
     setupTransition(t, 'margin-left 100s');
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     // Make idle
     transition.timeline = null;
     return watcher.wait_for('transitioncancel');
-  }).then(function(evt) {
+  }).then(evt => {
     transition.timeline = document.timeline;
     transition.play();
 
     return watcher.wait_for(['transitionrun', 'transitionstart']);
   });
 }, 'Set timeline and play transition after clear the timeline');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s');
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function() {
+                            'transitionstart' ]).then(() => {
     transition.cancel();
     return watcher.wait_for('transitioncancel');
-  }).then(function() {
+  }).then(() => {
     // Make After phase
     transition.effect = null;
 
     // Then wait a couple of frames and check that no event was dispatched
     return waitForAnimationFrames(2);
   });
 }, 'Set null target effect after cancel the transition');
 
-promise_test(function(t) {
-  var [transition, watcher, div] =
+promise_test(t => {
+  const { transition, watcher, div } =
     setupTransition(t, 'margin-left 100s');
 
   return watcher.wait_for([ 'transitionrun',
-                            'transitionstart' ]).then(function(evt) {
+                            'transitionstart' ]).then(evt => {
     transition.effect = null;
     return watcher.wait_for('transitionend');
-  }).then(function(evt) {
+  }).then(evt => {
     transition.cancel();
 
     // Then wait a couple of frames and check that no event was dispatched
     return waitForAnimationFrames(2);
   });
 }, 'Cancel the transition after clearing the target effect');
 
 done();
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1480,19 +1480,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream)
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
   tmp->RemoveMutationObserver(tmp);
   if (tmp->mSrcStream) {
     // Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
     // gets unhooked correctly.
     tmp->EndSrcMediaStreamPlayback();
@@ -1509,19 +1511,21 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLMediaElement)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 // nsIDOMHTMLMediaElement
 NS_IMPL_URI_ATTR(HTMLMediaElement, Src, src)
@@ -1717,16 +1721,17 @@ HTMLMediaElement::OnChannelRedirect(nsIC
   MOZ_ASSERT(mChannelLoader);
   return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
 }
 
 void HTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
+  mSetCDMRequest.DisconnectIfExists();
   mWaitingForKeyListener.DisconnectIfExists();
   if (mMediaSource) {
     mMediaSource->CompletePendingTransactions();
   }
   mDecoder->Shutdown();
   mDecoder = nullptr;
 }
 
@@ -3980,16 +3985,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mPreloadAction(PRELOAD_UNDEFINED),
     mLastCurrentTime(0.0),
     mFragmentStart(-1.0),
     mFragmentEnd(-1.0),
     mDefaultPlaybackRate(1.0),
     mPlaybackRate(1.0),
     mPreservesPitch(true),
     mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
+    mAttachingMediaKey(false),
     mCurrentPlayRangeStart(-1.0),
     mLoadedDataFired(false),
     mAutoplaying(true),
     mAutoplayEnabled(true),
     mPaused(true, *this),
     mStatsShowing(false),
     mAllowCasting(false),
     mIsCasting(false),
@@ -7010,16 +7016,190 @@ HTMLMediaElement::GetMediaKeys() const
 }
 
 bool
 HTMLMediaElement::ContainsRestrictedContent()
 {
   return GetMediaKeys() != nullptr;
 }
 
+void
+HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult)
+{
+  LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
+
+  ResetSetMediaKeysTempVariables();
+
+  mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message());
+}
+
+void
+HTMLMediaElement::RemoveMediaKeys()
+{
+  LOG(LogLevel::Debug, ("%s", __func__));
+  // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
+  // to decrypt media data and remove the association with the media element.
+  mMediaKeys->Unbind();
+  mMediaKeys = nullptr;
+}
+
+bool
+HTMLMediaElement::TryRemoveMediaKeysAssociation()
+{
+  MOZ_ASSERT(mMediaKeys);
+  LOG(LogLevel::Debug, ("%s", __func__));
+  // 5.2.1 If the user agent or CDM do not support removing the association,
+  // let this object's attaching media keys value be false and reject promise
+  // with a new DOMException whose name is NotSupportedError.
+  // 5.2.2 If the association cannot currently be removed, let this object's
+  // attaching media keys value be false and reject promise with a new
+  // DOMException whose name is InvalidStateError.
+  if (mDecoder) {
+    RefPtr<HTMLMediaElement> self = this;
+    mDecoder->SetCDMProxy(nullptr)
+      ->Then(mAbstractMainThread,
+             __func__,
+             [self]() {
+               self->mSetCDMRequest.Complete();
+
+               self->RemoveMediaKeys();
+               if (self->AttachNewMediaKeys()) {
+                 // No incoming MediaKeys object or MediaDecoder is not created yet.
+                 self->MakeAssociationWithCDMResolved();
+               }
+             },
+             [self](const MediaResult& aResult) {
+               self->mSetCDMRequest.Complete();
+               // 5.2.4 If the preceding step failed, let this object's attaching media
+               // keys value be false and reject promise with a new DOMException whose
+               // name is the appropriate error name.
+               self->SetCDMProxyFailure(aResult);
+             })
+      ->Track(mSetCDMRequest);
+    return false;
+  }
+
+  RemoveMediaKeys();
+  return true;
+}
+
+bool
+HTMLMediaElement::DetachExistingMediaKeys()
+{
+  LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
+  // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
+  // already in use by another media element, and the user agent is unable
+  // to use it with this element, let this object's attaching media keys
+  // value be false and reject promise with a new DOMException whose name
+  // is QuotaExceededError.
+  if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) {
+    SetCDMProxyFailure(MediaResult(
+      NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
+      "MediaKeys object is already bound to another HTMLMediaElement"));
+    return false;
+  }
+
+  // 5.2 If the mediaKeys attribute is not null, run the following steps:
+  if (mMediaKeys) {
+    return TryRemoveMediaKeysAssociation();
+  }
+  return true;
+}
+
+void
+HTMLMediaElement::MakeAssociationWithCDMResolved()
+{
+  LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
+
+  // 5.4 Set the mediaKeys attribute to mediaKeys.
+  mMediaKeys = mIncomingMediaKeys;
+  // 5.5 Let this object's attaching media keys value be false.
+  ResetSetMediaKeysTempVariables();
+  // 5.6 Resolve promise.
+  mSetMediaKeysDOMPromise->MaybeResolveWithUndefined();
+  mSetMediaKeysDOMPromise = nullptr;
+}
+
+bool
+HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy)
+{
+  LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(aProxy);
+
+  // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
+  // algorithm on the media element.
+  // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
+  if (mDecoder) {
+    // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
+    // HTMLMediaElement should resolve or reject the DOM promise.
+    RefPtr<HTMLMediaElement> self = this;
+    mDecoder->SetCDMProxy(aProxy)
+      ->Then(mAbstractMainThread,
+             __func__,
+             [self]() {
+               self->mSetCDMRequest.Complete();
+               self->MakeAssociationWithCDMResolved();
+             },
+             [self](const MediaResult& aResult) {
+               self->mSetCDMRequest.Complete();
+               self->SetCDMProxyFailure(aResult);
+             })
+      ->Track(mSetCDMRequest);
+    return false;
+  }
+  return true;
+}
+
+bool
+HTMLMediaElement::AttachNewMediaKeys()
+{
+  LOG(LogLevel::Debug,
+      ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get()));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
+
+  // 5.3. If mediaKeys is not null, run the following steps:
+  if (mIncomingMediaKeys) {
+    auto cdmProxy = mIncomingMediaKeys->GetCDMProxy();
+    if (!cdmProxy) {
+      SetCDMProxyFailure(MediaResult(
+        NS_ERROR_DOM_INVALID_STATE_ERR,
+        "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
+      return false;
+    }
+
+    // 5.3.1 Associate the CDM instance represented by mediaKeys with the
+    // media element for decrypting media data.
+    if (NS_FAILED(mIncomingMediaKeys->Bind(this))) {
+      // 5.3.2 If the preceding step failed, run the following steps:
+
+      // 5.3.2.1 Set the mediaKeys attribute to null.
+      mMediaKeys = nullptr;
+      // 5.3.2.2 Let this object's attaching media keys value be false.
+      // 5.3.2.3 Reject promise with a new DOMException whose name is
+      // the appropriate error name.
+      SetCDMProxyFailure(
+        MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+                    "Failed to bind MediaKeys object to HTMLMediaElement"));
+      return false;
+    }
+    return TryMakeAssociationWithCDM(cdmProxy);
+  }
+  return true;
+}
+
+void
+HTMLMediaElement::ResetSetMediaKeysTempVariables()
+{
+  mAttachingMediaKey = false;
+  mIncomingMediaKeys = nullptr;
+}
+
 already_AddRefed<Promise>
 HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
                                ErrorResult& aRv)
 {
   LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
     this, aMediaKeys, mMediaKeys.get(), mDecoder.get()));
 
   if (MozAudioCaptured()) {
@@ -7041,99 +7221,41 @@ HTMLMediaElement::SetMediaKeys(mozilla::
 
   // 1. If mediaKeys and the mediaKeys attribute are the same object,
   // return a resolved promise.
   if (mMediaKeys == aMediaKeys) {
     promise->MaybeResolveWithUndefined();
     return promise.forget();
   }
 
-  // Note: Our attaching code is synchronous, so we can skip the following steps.
-
   // 2. If this object's attaching media keys value is true, return a
   // promise rejected with a new DOMException whose name is InvalidStateError.
-  // 3. Let this object's attaching media keys value be true.
-  // 4. Let promise be a new promise.
-  // 5. Run the following steps in parallel:
-
-  // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
-  // already in use by another media element, and the user agent is unable
-  // to use it with this element, let this object's attaching media keys
-  // value be false and reject promise with a new DOMException whose name
-  // is QuotaExceededError.
-  if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
-    promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
-      NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement"));
+  if (mAttachingMediaKey) {
+    promise->MaybeReject(
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
     return promise.forget();
   }
 
-  // 5.2 If the mediaKeys attribute is not null, run the following steps:
-  if (mMediaKeys) {
-    // 5.2.1 If the user agent or CDM do not support removing the association,
-    // let this object's attaching media keys value be false and reject promise
-    // with a new DOMException whose name is NotSupportedError.
-
-    // 5.2.2 If the association cannot currently be removed, let this object's
-    // attaching media keys value be false and reject promise with a new
-    // DOMException whose name is InvalidStateError.
-    if (mDecoder) {
-      // We don't support swapping out the MediaKeys once we've started to
-      // setup the playback pipeline. Note this also means we don't need to worry
-      // about handling disassociating the MediaKeys from the MediaDecoder.
-      promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
-        NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started"));
-      return promise.forget();
-    }
-
-    // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
-    // to decrypt media data and remove the association with the media element.
-    mMediaKeys->Unbind();
-    mMediaKeys = nullptr;
-
-    // 5.2.4 If the preceding step failed, let this object's attaching media
-    // keys value be false and reject promise with a new DOMException whose
-    // name is the appropriate error name.
-  }
-
-  // 5.3. If mediaKeys is not null, run the following steps:
-  if (aMediaKeys) {
-    if (!aMediaKeys->GetCDMProxy()) {
-      promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
-        NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement"));
-      return promise.forget();
-    }
-
-    // 5.3.1 Associate the CDM instance represented by mediaKeys with the
-    // media element for decrypting media data.
-    if (NS_FAILED(aMediaKeys->Bind(this))) {
-      // 5.3.2 If the preceding step failed, run the following steps:
-      // 5.3.2.1 Set the mediaKeys attribute to null.
-      mMediaKeys = nullptr;
-      // 5.3.2.2 Let this object's attaching media keys value be false.
-      // 5.3.2.3 Reject promise with a new DOMException whose name is
-      // the appropriate error name.
-      promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
-                           NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement"));
-      return promise.forget();
-    }
-    // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
-    // algorithm on the media element.
-    // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
-    if (mDecoder) {
-      mDecoder->SetCDMProxy(aMediaKeys->GetCDMProxy());
-    }
-  }
-
-  // 5.4 Set the mediaKeys attribute to mediaKeys.
-  mMediaKeys = aMediaKeys;
-
-  // 5.5 Let this object's attaching media keys value be false.
-
-  // 5.6 Resolve promise.
-  promise->MaybeResolveWithUndefined();
+  // 3. Let this object's attaching media keys value be true.
+  mAttachingMediaKey = true;
+  mIncomingMediaKeys = aMediaKeys;
+
+  // 4. Let promise be a new promise.
+  mSetMediaKeysDOMPromise = promise;
+
+  // 5. Run the following steps in parallel:
+
+  // 5.1 & 5.2 & 5.3
+  if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
+    return promise.forget();
+  }
+
+  // 5.4, 5.5, 5.6
+  MakeAssociationWithCDMResolved();
 
   // 6. Return promise.
   return promise.forget();
 }
 
 EventHandlerNonNull*
 HTMLMediaElement::GetOnencrypted()
 {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -7,16 +7,17 @@
 #define mozilla_dom_HTMLMediaElement_h
 
 #include "nsAutoPtr.h"
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "MediaEventSource.h"
 #include "SeekTarget.h"
 #include "MediaDecoderOwner.h"
+#include "MediaPromiseDefs.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "mozilla/CORSMode.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TextTrackManager.h"
 #include "mozilla/WeakPtr.h"
@@ -1327,16 +1328,25 @@ protected:
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 bool aNotify) override;
   virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
                                           const nsAttrValueOrString& aValue,
                                           bool aNotify) override;
 
+  bool DetachExistingMediaKeys();
+  bool TryRemoveMediaKeysAssociation();
+  void RemoveMediaKeys();
+  bool AttachNewMediaKeys();
+  bool TryMakeAssociationWithCDM(CDMProxy* aProxy);
+  void MakeAssociationWithCDMResolved();
+  void SetCDMProxyFailure(const MediaResult& aResult);
+  void ResetSetMediaKeysTempVariables();
+
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
 
   // The DocGroup-specific nsISerialEventTarget of this HTML element on the main
   // thread.
   nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
 
@@ -1529,16 +1539,22 @@ protected:
   // Timer used for updating progress events.
   nsCOMPtr<nsITimer> mProgressTimer;
 
   // Timer used to simulate video-suspend.
   nsCOMPtr<nsITimer> mVideoDecodeSuspendTimer;
 
   // Encrypted Media Extension media keys.
   RefPtr<MediaKeys> mMediaKeys;
+  RefPtr<MediaKeys> mIncomingMediaKeys;
+  // The dom promise is used for HTMLMediaElement::SetMediaKeys.
+  RefPtr<DetailedPromise> mSetMediaKeysDOMPromise;
+  // Used to indicate if the MediaKeys attaching operation is on-going or not.
+  bool mAttachingMediaKey;
+  MozPromiseRequestHolder<SetCDMPromise> mSetCDMRequest;
 
   // Stores the time at the start of the current 'played' range.
   double mCurrentPlayRangeStart;
 
   // True if loadeddata has been fired.
   bool mLoadedDataFired;
 
   // Indicates whether current playback is a result of user action
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -9,16 +9,17 @@
 #include "ChannelMediaResource.h"
 #include "FileBlockCache.h"
 #include "MediaBlockCacheBase.h"
 #include "MediaPrefs.h"
 #include "MediaResource.h"
 #include "MemoryBlockCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ErrorNames.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentUtils.h"
@@ -30,19 +31,21 @@
 #include "nsThreadUtils.h"
 #include "prio.h"
 #include <algorithm>
 
 namespace mozilla {
 
 #undef LOG
 #undef LOGI
+#undef LOGE
 LazyLogModule gMediaCacheLog("MediaCache");
 #define LOG(...) MOZ_LOG(gMediaCacheLog, LogLevel::Debug, (__VA_ARGS__))
 #define LOGI(...) MOZ_LOG(gMediaCacheLog, LogLevel::Info, (__VA_ARGS__))
+#define LOGE(...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(__VA_ARGS__).get(), nullptr, __FILE__, __LINE__)
 
 // For HTTP seeking, if number of bytes needing to be
 // seeked forward is less than this value then a read is
 // done rather than a byte range request.
 //
 // If we assume a 100Mbit connection, and assume reissuing an HTTP seek causes
 // a delay of 200ms, then in that 200ms we could have simply read ahead 2MB. So
 // setting SEEK_VS_READ_THRESHOLD to 1MB sounds reasonable.
@@ -2010,16 +2013,19 @@ MediaCacheStream::NotifyDataReceived(uin
 
   if (mLoadID != aLoadID) {
     // mChannelOffset is updated to a new position when loading a new channel.
     // We should discard the data coming from the old channel so it won't be
     // stored to the wrong positoin.
     return;
   }
 
+  // True if we commit any blocks to the cache.
+  bool cacheUpdated = false;
+
   auto source = MakeSpan<const uint8_t>(aData, aCount);
 
   // We process the data one block (or part of a block) at a time
   while (!source.IsEmpty()) {
     // The data we've collected so far in the partial block.
     auto partial = MakeSpan<const uint8_t>(mPartialBlockBuffer.get(),
                                            OffsetInBlock(mChannelOffset));
 
@@ -2037,16 +2043,17 @@ MediaCacheStream::NotifyDataReceived(uin
       mMediaCache->AllocateAndWriteBlock(
         this,
         OffsetToBlockIndexUnchecked(mChannelOffset),
         mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK,
         partial,
         source.First(remaining));
       source = source.From(remaining);
       mChannelOffset += remaining;
+      cacheUpdated = true;
     } else {
       // The buffer to be filled in the partial block.
       auto buf = MakeSpan<uint8_t>(mPartialBlockBuffer.get() + partial.Length(),
                                    remaining);
       memcpy(buf.Elements(), source.Elements(), source.Length());
       mChannelOffset += source.Length();
       break;
     }
@@ -2056,20 +2063,22 @@ MediaCacheStream::NotifyDataReceived(uin
   while (MediaCacheStream* stream = iter.Next()) {
     if (stream->mStreamLength >= 0) {
       // The stream is at least as long as what we've read
       stream->mStreamLength = std::max(stream->mStreamLength, mChannelOffset);
     }
     stream->mClient->CacheClientNotifyDataReceived();
   }
 
-  // Notify in case there's a waiting reader
   // XXX it would be fairly easy to optimize things a lot more to
   // avoid waking up reader threads unnecessarily
-  mon.NotifyAll();
+  if (cacheUpdated) {
+    // Wake up the reader who is waiting for the committed blocks.
+    mon.NotifyAll();
+  }
 }
 
 void
 MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
                                             ReentrantMonitorAutoEnter& aReentrantMonitor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
@@ -2457,43 +2466,113 @@ MediaCacheStream::ThrottleReadahead(bool
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   if (mThrottleReadahead != bThrottle) {
     LOGI("Stream %p ThrottleReadahead %d", this, bThrottle);
     mThrottleReadahead = bThrottle;
     mMediaCache->QueueUpdate();
   }
 }
 
+uint32_t
+MediaCacheStream::ReadPartialBlock(int64_t aOffset, Span<char> aBuffer)
+{
+  mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
+  MOZ_ASSERT(IsOffsetAllowed(aOffset));
+
+  if (OffsetToBlockIndexUnchecked(mChannelOffset) !=
+        OffsetToBlockIndexUnchecked(aOffset) ||
+      aOffset >= mChannelOffset) {
+    // Not in the partial block or no data to read.
+    return 0;
+  }
+
+  auto source = MakeSpan<const uint8_t>(
+    mPartialBlockBuffer.get() + OffsetInBlock(aOffset),
+    OffsetInBlock(mChannelOffset) - OffsetInBlock(aOffset));
+  // We have |source.Length() <= BLOCK_SIZE < INT32_MAX| to guarantee
+  // that |bytesToRead| can fit into a uint32_t.
+  uint32_t bytesToRead = std::min(aBuffer.Length(), source.Length());
+  memcpy(aBuffer.Elements(), source.Elements(), bytesToRead);
+  return bytesToRead;
+}
+
+Result<uint32_t, nsresult>
+MediaCacheStream::ReadBlockFromCache(int64_t aOffset, Span<char> aBuffer)
+{
+  mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
+  MOZ_ASSERT(IsOffsetAllowed(aOffset));
+
+  // OffsetToBlockIndexUnchecked() is always non-negative.
+  uint32_t index = OffsetToBlockIndexUnchecked(aOffset);
+  int32_t cacheBlock = index < mBlocks.Length() ? mBlocks[index] : -1;
+  if (cacheBlock < 0) {
+    // Not in the cache.
+    return 0;
+  }
+
+  if (aBuffer.Length() > size_t(BLOCK_SIZE)) {
+    // Clamp the buffer to avoid overflow below since we will read at most
+    // BLOCK_SIZE bytes.
+    aBuffer = aBuffer.First(BLOCK_SIZE);
+  }
+  // |BLOCK_SIZE - OffsetInBlock(aOffset)| <= BLOCK_SIZE
+  int32_t bytesToRead =
+    std::min<int32_t>(BLOCK_SIZE - OffsetInBlock(aOffset), aBuffer.Length());
+  int32_t bytesRead = 0;
+  nsresult rv =
+    mMediaCache->ReadCacheFile(cacheBlock * BLOCK_SIZE + OffsetInBlock(aOffset),
+                               aBuffer.Elements(),
+                               bytesToRead,
+                               &bytesRead);
+
+  // Ensure |cacheBlock * BLOCK_SIZE + OffsetInBlock(aOffset)| won't overflow.
+  static_assert(INT64_MAX >= BLOCK_SIZE * (uint32_t(INT32_MAX) + 1),
+                "BLOCK_SIZE too large!");
+
+  if (NS_FAILED(rv)) {
+    nsCString name;
+    GetErrorName(rv, name);
+    LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data());
+    return mozilla::Err(rv);
+  }
+
+  return bytesRead;
+}
+
 int64_t
 MediaCacheStream::Tell()
 {
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   return mStreamOffset;
 }
 
 nsresult
 MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
-  if (mClosed)
-    return NS_ERROR_FAILURE;
 
   // Cache the offset in case it is changed again when we are waiting for the
   // monitor to be notified to avoid reading at the wrong position.
   auto streamOffset = mStreamOffset;
 
   uint32_t count = 0;
   // Read one block (or part of a block) at a time
   while (count < aCount) {
+    if (mClosed) {
+      return NS_ERROR_ABORT;
+    }
+
     int32_t streamBlock = OffsetToBlockIndex(streamOffset);
     if (streamBlock < 0) {
-      break;
+      LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset);
+      return NS_ERROR_ILLEGAL_VALUE;
     }
+
     uint32_t offsetInStreamBlock = uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
     int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
 
     if (mStreamLength >= 0) {
       // Don't try to read beyond the end of the stream
       int64_t bytesRemaining = mStreamLength - streamOffset;
       if (bytesRemaining <= 0) {
         // Get out of here and return NS_OK
@@ -2504,96 +2583,92 @@ MediaCacheStream::Read(char* aBuffer, ui
       size = std::min(size, int64_t(INT32_MAX));
     }
 
     int32_t cacheBlock =
       size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
     if (cacheBlock < 0) {
       // We don't have a complete cached block here.
 
-      if (count > 0) {
-        // Some data has been read, so return what we've got instead of
-        // blocking or trying to find a stream with a partial block.
-        break;
-      }
-
       // See if the data is available in the partial cache block of any
       // stream reading this resource. We need to do this in case there is
       // another stream with this resource that has all the data to the end of
       // the stream but the data doesn't end on a block boundary.
       MediaCacheStream* streamWithPartialBlock = nullptr;
       MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
       while (MediaCacheStream* stream = iter.Next()) {
         if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
               streamBlock &&
-            streamOffset < stream->mChannelOffset) {
+            streamOffset < stream->mChannelOffset &&
+            stream->mChannelOffset == stream->mStreamLength) {
           streamWithPartialBlock = stream;
           break;
         }
       }
       if (streamWithPartialBlock) {
         // We can just use the data in mPartialBlockBuffer. In fact we should
         // use it rather than waiting for the block to fill and land in
         // the cache.
         int64_t bytes = std::min<int64_t>(size, streamWithPartialBlock->mChannelOffset - streamOffset);
         // Clamp bytes until 64-bit file size issues are fixed.
         bytes = std::min(bytes, int64_t(INT32_MAX));
         MOZ_ASSERT(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
-        memcpy(aBuffer,
-          streamWithPartialBlock->mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
+        memcpy(aBuffer + count,
+               streamWithPartialBlock->mPartialBlockBuffer.get() +
+                 offsetInStreamBlock,
+               bytes);
         if (mCurrentMode == MODE_METADATA) {
           streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
         }
         streamOffset += bytes;
-        count = bytes;
+        count += bytes;
+        // Break for we've reached EOS and have nothing more to read.
         break;
       }
 
       if (mStreamOffset != streamOffset) {
         // Updat mStreamOffset before we drop the lock. We need to run
         // Update() again since stream reading strategy might have changed.
         mStreamOffset = streamOffset;
         mMediaCache->QueueUpdate();
       }
 
       // No data has been read yet, so block
       mon.Wait();
-      if (mClosed) {
-        // We may have successfully read some data, but let's just throw
-        // that out.
-        return NS_ERROR_FAILURE;
-      }
       continue;
     }
 
     mMediaCache->NoteBlockUsage(
       this, cacheBlock, streamOffset, mCurrentMode, TimeStamp::Now());
 
     int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
     int32_t bytes;
     MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range.");
     nsresult rv = mMediaCache->ReadCacheFile(
       offset, aBuffer + count, int32_t(size), &bytes);
     if (NS_FAILED(rv)) {
-      if (count == 0)
-        return rv;
-      // If we did successfully read some data, may as well return it
-      break;
+      nsCString name;
+      GetErrorName(rv, name);
+      LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data());
+      return rv;
     }
     streamOffset += bytes;
     count += bytes;
   }
 
-  if (count > 0) {
-    // Some data was read, so queue an update since block priorities may
-    // have changed
-    mMediaCache->QueueUpdate();
+  *aBytes = count;
+  if (count == 0) {
+    return NS_OK;
   }
+
+  // Some data was read, so queue an update since block priorities may
+  // have changed
+  mMediaCache->QueueUpdate();
+
   LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
-  *aBytes = count;
   mStreamOffset = streamOffset;
   return NS_OK;
 }
 
 nsresult
 MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
                          uint32_t aCount, uint32_t* aBytes)
 {
@@ -2601,77 +2676,59 @@ MediaCacheStream::ReadAt(int64_t aOffset
 
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
   nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   if (NS_FAILED(rv)) return rv;
   return Read(aBuffer, aCount, aBytes);
 }
 
 nsresult
-MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount)
+MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
 {
   ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
 
+  // The buffer we are about to fill.
+  auto buffer = MakeSpan<char>(aBuffer, aCount);
+
   // Read one block (or part of a block) at a time
-  uint32_t count = 0;
   int64_t streamOffset = aOffset;
-  while (count < aCount) {
+  while (!buffer.IsEmpty()) {
     if (mClosed) {
       // We need to check |mClosed| in each iteration which might be changed
       // after calling |mMediaCache->ReadCacheFile|.
       return NS_ERROR_FAILURE;
     }
-    int32_t streamBlock = OffsetToBlockIndex(streamOffset);
-    if (streamBlock < 0) {
-      break;
-    }
-    uint32_t offsetInStreamBlock =
-      uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
-    int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
 
-    if (mStreamLength >= 0) {
-      // Don't try to read beyond the end of the stream
-      int64_t bytesRemaining = mStreamLength - streamOffset;
-      if (bytesRemaining <= 0) {
-        return NS_ERROR_FAILURE;
-      }
-      size = std::min(size, bytesRemaining);
-      // Clamp size until 64-bit file size issues are fixed.
-      size = std::min(size, int64_t(INT32_MAX));
+    if (!IsOffsetAllowed(streamOffset)) {
+      LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset);
+      return NS_ERROR_ILLEGAL_VALUE;
+    }
+
+    Result<uint32_t, nsresult> rv = ReadBlockFromCache(streamOffset, buffer);
+    if (rv.isErr()) {
+      return rv.unwrapErr();
     }
 
-    int32_t bytes;
-    int32_t channelBlock = OffsetToBlockIndexUnchecked(mChannelOffset);
-    int32_t cacheBlock =
-      size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
-    if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
-      // We can just use the data in mPartialBlockBuffer. In fact we should
-      // use it rather than waiting for the block to fill and land in
-      // the cache.
-      // Clamp bytes until 64-bit file size issues are fixed.
-      int64_t toCopy = std::min<int64_t>(size, mChannelOffset - streamOffset);
-      bytes = std::min(toCopy, int64_t(INT32_MAX));
-      MOZ_ASSERT(bytes >= 0 && bytes <= toCopy, "Bytes out of range.");
-      memcpy(aBuffer + count,
-        mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
-    } else {
-      if (cacheBlock < 0) {
-        // We expect all blocks to be cached! Fail!
-        return NS_ERROR_FAILURE;
-      }
-      int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
-      MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range.");
-      nsresult rv = mMediaCache->ReadCacheFile(
-        offset, aBuffer + count, int32_t(size), &bytes);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
+    uint32_t bytes = rv.unwrap();
+    if (bytes > 0) {
+      // Read data from the cache successfully. Let's try next block.
+      streamOffset += bytes;
+      buffer = buffer.From(bytes);
+      continue;
     }
-    streamOffset += bytes;
-    count += bytes;
+
+    // The partial block is our last chance to get data.
+    bytes = ReadPartialBlock(streamOffset, buffer);
+    if (bytes < buffer.Length()) {
+      // Not enough data to read.
+      return NS_ERROR_FAILURE;
+    }
+
+    // Return for we've got all the requested bytes.
+    return NS_OK;
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaCacheStream::Init(int64_t aContentLength)
 {
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -3,16 +3,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/. */
 
 #ifndef MediaCache_h_
 #define MediaCache_h_
 
 #include "Intervals.h"
+#include "mozilla/Result.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 class nsIEventTarget;
 class nsIPrincipal;
@@ -309,19 +310,17 @@ public:
   // growing. The stream should be pinned while this runs and while its results
   // are used, to ensure no data is evicted.
   nsresult GetCachedRanges(MediaByteRangeSet& aRanges);
 
   // Reads from buffered data only. Will fail if not all data to be read is
   // in the cache. Will not mark blocks as read. Can be called from the main
   // thread. It's the caller's responsibility to wrap the call in a pin/unpin,
   // and also to check that the range they want is cached before calling this.
-  nsresult ReadFromCache(char* aBuffer,
-                         int64_t aOffset,
-                         int64_t aCount);
+  nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
 
   // IsDataCachedToEndOfStream returns true if all the data from
   // aOffset to the end of the stream (the server-reported end, if the
   // real end is not known) is in cache. If we know nothing about the
   // end of the stream, this returns false.
   bool IsDataCachedToEndOfStream(int64_t aOffset);
   // The mode is initially MODE_PLAYBACK.
   void SetReadMode(ReadMode aMode);
@@ -421,16 +420,26 @@ private:
     nsTHashtable<Entry> mEntries;
 
     // The index of the first block in the list, or -1 if the list is empty.
     int32_t mFirstBlock;
     // The number of blocks in the list.
     int32_t mCount;
   };
 
+  // Read data from the partial block and return the number of bytes read
+  // successfully. 0 if aOffset is not an offset in the partial block or there
+  // is nothing to read.
+  uint32_t ReadPartialBlock(int64_t aOffset, Span<char> aBuffer);
+
+  // Read data from the cache block specified by aOffset. Return the number of
+  // bytes read successfully or an error code if any failure.
+  Result<uint32_t, nsresult> ReadBlockFromCache(int64_t aOffset,
+                                                Span<char> aBuffer);
+
   // Returns the end of the bytes starting at the given offset
   // which are in cache.
   // This method assumes that the cache monitor is held and can be called on
   // any thread.
   int64_t GetCachedDataEndInternal(int64_t aOffset);
   // Returns the offset of the first byte of cached data at or after aOffset,
   // or -1 if there is no such cached data.
   // This method assumes that the cache monitor is held and can be called on
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1418,28 +1418,25 @@ MediaDecoder::CanPlayThrough()
   bool val = CanPlayThroughImpl();
   if (val != mCanPlayThrough) {
     mCanPlayThrough = val;
     mDecoderStateMachine->DispatchCanPlayThrough(val);
   }
   return val;
 }
 
-void
+RefPtr<SetCDMPromise>
 MediaDecoder::SetCDMProxy(CDMProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  RefPtr<CDMProxy> proxy = aProxy;
-  RefPtr<MediaFormatReader> reader = mReader;
-  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
-    "MediaFormatReader::SetCDMProxy",
-    [reader, proxy]() {
-    reader->SetCDMProxy(proxy);
-    });
-  mReader->OwnerThread()->Dispatch(r.forget());
+  return InvokeAsync<RefPtr<CDMProxy>>(mReader->OwnerThread(),
+                                       mReader.get(),
+                                       __func__,
+                                       &MediaFormatReader::SetCDMProxy,
+                                       aProxy);
 }
 
 bool
 MediaDecoder::IsOpusEnabled()
 {
   return Preferences::GetBool("media.opus.enabled");
 }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -7,16 +7,17 @@
 #if !defined(MediaDecoder_h_)
 #define MediaDecoder_h_
 
 #include "DecoderDoctorDiagnostics.h"
 #include "MediaContainerType.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
+#include "MediaPromiseDefs.h"
 #include "MediaResource.h"
 #include "MediaStatistics.h"
 #include "MediaStreamGraph.h"
 #include "SeekTarget.h"
 #include "TimeUnits.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/MozPromise.h"
@@ -350,17 +351,17 @@ private:
 
   MediaDecoderOwner* GetOwner() const;
 
   AbstractThread* AbstractMainThread() const
   {
     return mAbstractMainThread;
   }
 
-  void SetCDMProxy(CDMProxy* aProxy);
+  RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy);
 
   void EnsureTelemetryReported();
 
   static bool IsOggEnabled();
   static bool IsOpusEnabled();
   static bool IsWaveEnabled();
   static bool IsWebMEnabled();
 
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1223,16 +1223,20 @@ MediaFormatReader::Shutdown()
   MOZ_ASSERT(OnTaskQueue());
   LOG("");
 
   mDemuxerInitRequest.DisconnectIfExists();
   mNotifyDataArrivedPromise.DisconnectIfExists();
   mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   mSkipRequest.DisconnectIfExists();
+  mSetCDMPromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+                "MediaFormatReader is shutting down"),
+    __func__);
 
   if (mAudio.HasPromise()) {
     mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
   if (mVideo.HasPromise()) {
     mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
@@ -1313,33 +1317,111 @@ MediaFormatReader::Init()
 
   mVideo.mTaskQueue = new TaskQueue(
     GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
     "MFR::mVideo::mTaskQueue");
 
   return NS_OK;
 }
 
+bool
+MediaFormatReader::ResolveSetCDMPromiseIfDone(TrackType aTrack)
+{
+  // When a CDM proxy is set, MFR would shutdown the existing MediaDataDecoder
+  // and would create new one for specific track in the next Update.
+  MOZ_ASSERT(OnTaskQueue());
+
+  if (mSetCDMPromise.IsEmpty()) {
+    return true;
+  }
+
+  MOZ_ASSERT(mCDMProxy);
+  if (mSetCDMForTracks.contains(aTrack)) {
+    mSetCDMForTracks -= aTrack;
+  }
+
+  if (mSetCDMForTracks.isEmpty()) {
+    LOGV("%s : Done ", __func__);
+    mSetCDMPromise.Resolve(/* aIgnored = */ true, __func__);
+    ScheduleUpdate(TrackInfo::kAudioTrack);
+    ScheduleUpdate(TrackInfo::kVideoTrack);
+    return true;
+  }
+  LOGV("%s : %s track is ready.", __func__, TrackTypeToStr(aTrack));
+  return false;
+}
+
 void
+MediaFormatReader::PrepareToSetCDMForTrack(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  LOGV("%s : %s", __func__, TrackTypeToStr(aTrack));
+
+  mSetCDMForTracks += aTrack;
+  if (mCDMProxy) {
+    // An old cdm proxy exists, so detaching old cdm proxy by shutting down
+    // MediaDataDecoder.
+    ShutdownDecoder(aTrack);
+  }
+  ScheduleUpdate(aTrack);
+}
+
+bool
+MediaFormatReader::IsDecoderWaitingForCDM(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return IsEncrypted() && mSetCDMForTracks.contains(aTrack) && !mCDMProxy;
+}
+
+RefPtr<SetCDMPromise>
 MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
 {
-  RefPtr<CDMProxy> proxy = aProxy;
-  RefPtr<MediaFormatReader> self = this;
-  nsCOMPtr<nsIRunnable> r =
-    NS_NewRunnableFunction("MediaFormatReader::SetCDMProxy", [=]() {
-      MOZ_ASSERT(self->OnTaskQueue());
-      self->mCDMProxy = proxy;
-      if (HasAudio()) {
-        self->ScheduleUpdate(TrackInfo::kAudioTrack);
-      }
-      if (HasVideo()) {
-        self->ScheduleUpdate(TrackInfo::kVideoTrack);
-      }
-    });
-  OwnerThread()->Dispatch(r.forget());
+  MOZ_ASSERT(OnTaskQueue());
+  LOGV("SetCDMProxy (%p)", aProxy);
+
+  if (mShutdown) {
+    return SetCDMPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+                  "MediaFormatReader is shutting down"),
+      __func__);
+  }
+
+  mSetCDMPromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+                "Another new CDM proxy is being set."),
+    __func__);
+
+  // Shutdown all decoders as switching CDM proxy indicates that it's
+  // inappropriate for the existing decoders to continue decoding via the old
+  // CDM proxy.
+  if (HasAudio()) {
+    PrepareToSetCDMForTrack(TrackInfo::kAudioTrack);
+  }
+  if (HasVideo()) {
+    PrepareToSetCDMForTrack(TrackInfo::kVideoTrack);
+  }
+
+  mCDMProxy = aProxy;
+
+  if (IsEncrypted() && !mCDMProxy) {
+    // Release old PDMFactory which contains an EMEDecoderModule.
+    mPlatform = nullptr;
+  }
+
+  if (!mInitDone || mSetCDMForTracks.isEmpty() || !mCDMProxy) {
+    // 1) MFR is not initialized yet or
+    // 2) Demuxer is initialized without active audio and video or
+    // 3) A null cdm proxy is set
+    // the promise can be resolved directly.
+    mSetCDMForTracks.clear();
+    return SetCDMPromise::CreateAndResolve(/* aIgnored = */ true, __func__);
+  }
+
+  RefPtr<SetCDMPromise> p = mSetCDMPromise.Ensure(__func__);
+  return p;
 }
 
 bool
 MediaFormatReader::IsWaitingOnCDMResource()
 {
   MOZ_ASSERT(OnTaskQueue());
   return IsEncrypted() && !mCDMProxy;
 }
@@ -2386,16 +2468,23 @@ MediaFormatReader::Update(TrackType aTra
       // There is no more samples left to be decoded and we are already in
       // EOS state. We can immediately reject the data promise.
       LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
       decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
     } else if (decoder.mWaitingForKey) {
       LOG("Rejecting %s promise: WAITING_FOR_DATA due to waiting for key",
           TrackTypeToStr(aTrack));
       decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
+    } else if (IsDecoderWaitingForCDM(aTrack)) {
+      // Rejecting the promise could lead to entering buffering state for MDSM,
+      // once a qualified(with the same key system and sessions created by the
+      // same InitData) new cdm proxy is set, decoding can be resumed.
+      LOG("Rejecting %s promise: WAITING_FOR_DATA due to waiting for CDM",
+          TrackTypeToStr(aTrack));
+      decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
     }
   }
 
   if (decoder.mDrainState == DrainState::DrainRequested ||
       decoder.mDrainState == DrainState::PartialDrainPending) {
     if (decoder.mOutput.IsEmpty()) {
       DrainDecoder(aTrack);
     }
@@ -2443,43 +2532,46 @@ MediaFormatReader::Update(TrackType aTra
     }
     return;
   }
 
   bool needInput = NeedInput(decoder);
 
   LOGV("Update(%s) ni=%d no=%d in:%" PRIu64 " out:%" PRIu64
        " qs=%u decoding:%d flushing:%d desc:%s pending:%u waiting:%d eos:%d "
-       "ds:%d sid:%u",
+       "ds:%d sid:%u waitcdm:%d",
        TrackTypeToStr(aTrack),
        needInput,
        needOutput,
        decoder.mNumSamplesInput,
        decoder.mNumSamplesOutput,
        uint32_t(size_t(decoder.mSizeOfQueue)),
        decoder.mDecodeRequest.Exists(),
        decoder.mFlushing,
        decoder.mDescription.get(),
        uint32_t(decoder.mOutput.Length()),
        decoder.mWaitingForData,
        decoder.mDemuxEOS,
        int32_t(decoder.mDrainState),
-       decoder.mLastStreamSourceID);
-
-  if (IsWaitingOnCDMResource()) {
+       decoder.mLastStreamSourceID,
+       IsDecoderWaitingForCDM(aTrack));
+
+  if (IsWaitingOnCDMResource() || !ResolveSetCDMPromiseIfDone(aTrack)) {
     // If the content is encrypted, MFR won't start to create decoder until
     // CDMProxy is set.
     return;
   }
 
   if ((decoder.mWaitingForData &&
        (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) ||
       (decoder.mWaitingForKey && decoder.mDecodeRequest.Exists())) {
     // Nothing more we can do at present.
-    LOGV("Still waiting for data or key.");
+    LOGV("Still waiting for data or key. data(%d)/key(%d)",
+         decoder.mWaitingForData,
+         decoder.mWaitingForKey);
     return;
   }
 
   if (decoder.CancelWaitingForKey()) {
     LOGV("No longer waiting for key. Resolving waiting promise");
     return;
   }
 
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -13,16 +13,17 @@
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Mutex.h"
 
 #include "FrameStatistics.h"
 #include "MediaEventSource.h"
 #include "MediaDataDemuxer.h"
 #include "MediaMetadataManager.h"
 #include "MediaPrefs.h"
+#include "MediaPromiseDefs.h"
 #include "nsAutoPtr.h"
 #include "PDMFactory.h"
 #include "SeekTarget.h"
 
 namespace mozilla {
 
 class CDMProxy;
 class GMPCrashHelper;
@@ -86,17 +87,16 @@ struct MOZ_STACK_CLASS MediaFormatReader
   MediaDecoderOwnerID mMediaDecoderOwnerID = nullptr;
 };
 
 class MediaFormatReader final
 {
   static const bool IsExclusive = true;
   typedef TrackInfo::TrackType TrackType;
   typedef MozPromise<bool, MediaResult, IsExclusive> NotifyDataArrivedPromise;
-
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaFormatReader)
 
 public:
   using TrackSet = EnumSet<TrackInfo::TrackType>;
   using MetadataPromise = MozPromise<MetadataHolder, MediaResult, IsExclusive>;
 
   template<typename Type>
   using DataPromise = MozPromise<RefPtr<Type>, MediaResult, IsExclusive>;
@@ -189,17 +189,17 @@ public:
   RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType);
 
   // The MediaDecoderStateMachine uses various heuristics that assume that
   // raw media data is arriving sequentially from a network channel. This
   // makes sense in the <video src="foo"> case, but not for more advanced use
   // cases like MSE.
   bool UseBufferingHeuristics() const { return mTrackDemuxersMayBlock; }
 
-  void SetCDMProxy(CDMProxy* aProxy);
+  RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy);
 
   // Returns a string describing the state of the decoder data.
   // Used for debugging purposes.
   void GetMozDebugReaderData(nsACString& aString);
 
   // Switch the video decoder to NullDecoderModule. It might takes effective
   // since a few samples later depends on how much demuxed samples are already
   // queued in the original video decoder.
@@ -787,13 +787,19 @@ private:
   MediaEventProducer<void> mOnWaitingForKey;
 
   MediaEventProducer<MediaResult> mOnDecodeWarning;
 
   RefPtr<FrameStatistics> mFrameStats;
 
   // Used in bug 1393399 for telemetry.
   const MediaDecoderOwnerID mMediaDecoderOwnerID;
+
+  bool ResolveSetCDMPromiseIfDone(TrackType aTrack);
+  void PrepareToSetCDMForTrack(TrackType aTrack);
+  MozPromiseHolder<SetCDMPromise> mSetCDMPromise;
+  TrackSet mSetCDMForTracks{};
+  bool IsDecoderWaitingForCDM(TrackType aTrack);
 };
 
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaPromiseDefs.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 MediaPromiseDefs_h_
+#define MediaPromiseDefs_h_
+
+#include "MediaResult.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla {
+
+using SetCDMPromise =
+  MozPromise<bool /* aIgnored */, MediaResult, /* IsExclusive */ true>;
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -409,83 +409,42 @@ MediaResourceIndex::CacheOrReadAt(int64_
 }
 
 nsresult
 MediaResourceIndex::UncachedReadAt(int64_t aOffset,
                                    char* aBuffer,
                                    uint32_t aCount,
                                    uint32_t* aBytes) const
 {
-  *aBytes = 0;
   if (aOffset < 0) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
-  if (aCount != 0) {
-    for (;;) {
-      uint32_t bytesRead = 0;
-      nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-      if (bytesRead == 0) {
-        break;
-      }
-      *aBytes += bytesRead;
-      aCount -= bytesRead;
-      if (aCount == 0) {
-        break;
-      }
-      aOffset += bytesRead;
-      if (aOffset < 0) {
-        // Very unlikely overflow.
-        return NS_ERROR_FAILURE;
-      }
-      aBuffer += bytesRead;
-    }
+  if (aCount == 0) {
+    *aBytes = 0;
+    return NS_OK;
   }
-  return NS_OK;
+  return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
 }
 
 nsresult
 MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
                                          char* aBuffer,
                                          uint32_t aRequestedCount,
                                          uint32_t aExtraCount,
                                          uint32_t* aBytes) const
 {
-  *aBytes = 0;
   uint32_t count = aRequestedCount + aExtraCount;
   if (aOffset < 0 || count < aRequestedCount) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
-  if (count != 0) {
-    for (;;) {
-      uint32_t bytesRead = 0;
-      nsresult rv = mResource->ReadAt(aOffset, aBuffer, count, &bytesRead);
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-      if (bytesRead == 0) {
-        break;
-      }
-      *aBytes += bytesRead;
-      count -= bytesRead;
-      if (count <= aExtraCount) {
-        // We have read at least aRequestedCount, don't loop anymore.
-        break;
-      }
-      aOffset += bytesRead;
-      if (aOffset < 0) {
-        // Very unlikely overflow.
-        return NS_ERROR_FAILURE;
-      }
-      aBuffer += bytesRead;
-    }
+  if (count == 0) {
+    *aBytes = 0;
+    return NS_OK;
   }
-  return NS_OK;
+  return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
 }
 
 nsresult
 MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
 {
   switch (aWhence) {
     case SEEK_SET:
       break;
@@ -511,40 +470,27 @@ MediaResourceIndex::Seek(int32_t aWhence
   mOffset = aOffset;
 
   return NS_OK;
 }
 
 already_AddRefed<MediaByteBuffer>
 MediaResourceIndex::MediaReadAt(int64_t aOffset, uint32_t aCount) const
 {
+  NS_ENSURE_TRUE(aOffset >= 0, nullptr);
   RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
-  if (aOffset < 0) {
-    return bytes.forget();
-  }
   bool ok = bytes->SetLength(aCount, fallible);
   NS_ENSURE_TRUE(ok, nullptr);
-  char* curr = reinterpret_cast<char*>(bytes->Elements());
-  const char* start = curr;
-  while (aCount > 0) {
-    uint32_t bytesRead;
-    nsresult rv = mResource->ReadAt(aOffset, curr, aCount, &bytesRead);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-    if (!bytesRead) {
-      break;
-    }
-    aOffset += bytesRead;
-    if (aOffset < 0) {
-      // Very unlikely overflow.
-      break;
-    }
-    aCount -= bytesRead;
-    curr += bytesRead;
-  }
-  bytes->SetLength(curr - start);
+
+  uint32_t bytesRead = 0;
+  nsresult rv = mResource->ReadAt(
+    aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  bytes->SetLength(bytesRead);
   return bytes.forget();
 }
 
 already_AddRefed<MediaByteBuffer>
 MediaResourceIndex::CachedMediaReadAt(int64_t aOffset, uint32_t aCount) const
 {
   RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
   bool ok = bytes->SetLength(aCount, fallible);
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -116,16 +116,17 @@ EXPORTS += [
     'MediaDecoderOwner.h',
     'MediaDecoderStateMachine.h',
     'MediaEventSource.h',
     'MediaFormatReader.h',
     'MediaInfo.h',
     'MediaMetadataManager.h',
     'MediaMIMETypes.h',
     'MediaPrefs.h',
+    'MediaPromiseDefs.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaResourceCallback.h',
     'MediaResult.h',
     'MediaSegment.h',
     'MediaShutdownManager.h',
     'MediaStatistics.h',
new file mode 100644
--- /dev/null
+++ b/dom/media/test/bipbop_225w_175kbps.mp4^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -69,16 +69,18 @@ support-files =
   bipbop-cenc-video1.m4s
   bipbop-cenc-video1.m4s^headers^
   bipbop-cenc-video2.m4s
   bipbop-cenc-video2.m4s^headers^
   bipbop-cenc-videoinit.mp4
   bipbop-cenc-videoinit.mp4^headers^
   bipbop-cenc-video-10s.mp4
   bipbop-cenc-video-10s.mp4^headers^
+  bipbop_225w_175kbps.mp4
+  bipbop_225w_175kbps.mp4^headers^
   bipbop_225w_175kbps-cenc-audio-key1-1.m4s
   bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^
   bipbop_225w_175kbps-cenc-audio-key1-2.m4s
   bipbop_225w_175kbps-cenc-audio-key1-2.m4s^headers^
   bipbop_225w_175kbps-cenc-audio-key1-3.m4s
   bipbop_225w_175kbps-cenc-audio-key1-3.m4s^headers^
   bipbop_225w_175kbps-cenc-audio-key1-4.m4s
   bipbop_225w_175kbps-cenc-audio-key1-4.m4s^headers^
@@ -753,16 +755,18 @@ skip-if = android_version == '17' # andr
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_pssh_in_moof.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_session_callable_value.html]
 [test_eme_canvas_blocked.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_detach_media_keys.html]
 skip-if = toolkit == 'android' # bug 1149374
+[test_eme_detach_reattach_same_mediakeys_during_playback.html]
+skip-if = toolkit == 'android' # bug 1149374
 [test_eme_initDataTypes.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_missing_pssh.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_non_mse_fails.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_request_notifications.html]
 skip-if = toolkit == 'android' # bug 1149374
@@ -778,16 +782,18 @@ skip-if = toolkit == 'android' # bug 114
 tags=msg capturestream
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_stream_capture_blocked_case2.html]
 tags=msg capturestream
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_stream_capture_blocked_case3.html]
 tags=msg capturestream
 skip-if = toolkit == 'android' # bug 1149374
+[test_eme_unsetMediaKeys_then_capture.html]
+skip-if = toolkit == 'android' # bug 1149374
 [test_eme_waitingforkey.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_empty_resource.html]
 [test_error_in_video_document.html]
 [test_error_on_404.html]
 [test_fastSeek.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_fastSeek-forwards.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_eme_detach_reattach_same_mediakeys_during_playback.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Encrypted Media Extensions</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>
+  <script type="text/javascript" src="http://test1.mochi.test:8888/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<video id="v" controls></video>
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+var EMEmanifest = [
+  {
+    name:"bipbop 10s",
+    tracks: [
+      {
+          name:"video",
+          type:"video/mp4; codecs=\"avc1.4d4015\"",
+          fragments:[ "bipbop-cenc-video-10s.mp4",
+                    ]
+      }
+    ],
+    keys: {
+      "7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311",
+    },
+    sessionType:"temporary",
+    sessionCount:1,
+    duration:10.01
+  },
+];
+
+function sleep(time) {
+  return new Promise((resolve) => setTimeout(resolve, time));
+}
+
+// To check if playback can be blocked and resumed correctly after
+// detaching original mediakeys and reattach it back.
+function startTest(test, token)
+{
+  manager.started(token);
+
+  var mk_ori;
+  let finish = new EMEPromise;
+
+  let v = document.getElementById("v");
+  let sessions = [];
+  function onSessionCreated(session) {
+    sessions.push(session);
+  }
+
+  function closeSessions() {
+    let p = new EMEPromise;
+    Promise.all(sessions.map(s => s.close()))
+    .then(p.resolve, p.reject);
+    return p.promise;
+  }
+
+  function setMediaKeysToElement(mk, solve, reject) {
+    v.setMediaKeys(mk).then(solve, reject);
+  }
+
+  function ReattachOriMediaKeys() {
+    function onOriMediaKeysSetOK() {
+      ok(true, TimeStamp(token) + " (ENCRYPTED) Set original MediaKeys back OK!");
+    }
+    function onOriMediaKeysSetFailed() {
+      ok(false, " Failed to set original mediakeys back.");
+    }
+
+    function onCanPlayAgain(ev) {
+      Promise.all([closeSessions()])
+      .then(() => {
+        ok(true, " (ENCRYPTED) Playback can be resumed.");
+        manager.finished(token);
+      }, () => {
+        ok(false, TimeStamp(token) + " Sessions are closed incorrectly.");
+        manager.finished(token);
+      });
+    }
+
+    once(v, "canplay", onCanPlayAgain);
+    setMediaKeysToElement(mk_ori, onOriMediaKeysSetOK, onOriMediaKeysSetFailed)
+  }
+
+  function triggerSeek() {
+    v.currentTime = v.duration / 2;
+  }
+
+  function onCanPlay(ev) {
+    function onSetMediaKeysToNullOK() {
+      ok(true, TimeStamp(token) + " Set MediaKeys to null. OK!");
+
+      triggerSeek();
+
+      SimpleTest.requestFlakyTimeout("To reattach mediakeys back again in 5s.");
+      sleep(5000).then(ReattachOriMediaKeys);
+    }
+    function onSetMediaKeysToNullFailed() {
+      ok(false, TimeStamp(token) + " Set MediaKeys to null. FAILED!");
+    }
+
+    SimpleTest.requestFlakyTimeout("To detach mediakeys after receiving 'canplay' event in 2s");
+    sleep(2000).then(() => {
+      setMediaKeysToElement(null, onSetMediaKeysToNullOK, onSetMediaKeysToNullFailed);
+    });
+  }
+
+  once(v, "canplay", onCanPlay);
+
+  var p1 = LoadInitData(v, test, token);
+  var p2 = CreateAndSetMediaKeys(v, test, token);
+  var p3 = LoadTest(test, v, token);
+  Promise.all([p1, p2, p3])
+  .then(values => {
+    let initData = values[0];
+    // stash the mediakeys
+    mk_ori = v.mediaKeys;
+    initData.map(ev => {
+      let session = v.mediaKeys.createSession();
+      onSessionCreated(session);
+      MakeRequest(test, token, ev, session);
+    });
+  })
+  .then(() => {
+    return finish.promise;
+  })
+  .catch(reason => ok(false, reason))
+}
+
+function beginTest() {
+  manager.runTests(EMEmanifest, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_eme_unsetMediaKeys_then_capture.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Encrypted Media Extensions</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>
+  <script type="text/javascript" src="http://test1.mochi.test:8888/tests/dom/media/test/eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var manager = new MediaTestManager;
+
+// Test that if we can capture a video frame while playing clear content after
+// removing the MediaKeys object which was used for a previous encrypted content
+// playback on the same video element
+function startTest(test, token)
+{
+  manager.started(token);
+  var sessions = [];
+  function onSessionCreated(session) {
+    sessions.push(session);
+  }
+
+  function closeSessions() {
+    let p = new EMEPromise;
+    Promise.all(sessions.map(s => s.close()))
+    .then(p.resolve, p.reject);
+    return p.promise;
+  }
+
+  let v = document.createElement("video");
+  document.body.appendChild(v);
+
+  let finish = new EMEPromise;
+
+  function onVideoEnded(ev) {
+    ok(true, TimeStamp(token) + " (ENCRYPTED) content playback ended.");
+    v.removeEventListener("ended", onVideoEnded);
+
+    function playClearVideo() {
+      var p1 = once(v, 'ended', (e) => {
+        ok(true, TimeStamp(token) + " (CLEAR) content playback ended.");
+        console.log(" bipbop.mp4 playback ended !!");
+      });
+      var p2 = once(v, 'loadeddata', (e) => {
+        ok(true, TimeStamp(token) + " Receiving event 'loadeddata' for (CLEAR) content.");
+        canvasElem = document.createElement('canvas');
+        document.body.appendChild(canvasElem);
+        ctx2d = canvasElem.getContext('2d');
+
+        var gotTypeError = false;
+        try {
+          ctx2d.drawImage(v, 0, 0);
+        } catch (e) {
+          if (e instanceof TypeError) {
+            gotTypeError = true;
+          }
+        }
+        ok(!gotTypeError, TimeStamp(token) + " Canvas2D context drawImage succeed.")
+      });
+      v.src = 'bipbop_225w_175kbps.mp4';
+      v.play();
+      Promise.all([p1, p2, closeSessions()]).then(() => {
+        manager.finished(token);
+      }, () => {
+        ok(false, TimeStamp(token) + " Something wrong.");
+        manager.finished(token);
+      });
+    }
+
+    Promise.all(sessions.map(s => s.close()))
+    .then(() => {
+      v.setMediaKeys(null)
+      .then(() => {
+        ok(true, TimeStamp(token) + " Setting MediaKeys to null.");
+        playClearVideo();
+      }, () => {
+        ok(false, TimeStamp(token) + " Setting MediaKeys to null.");
+      });;
+    });
+  }
+
+  v.addEventListener("ended", onVideoEnded);
+
+  // Create a MediaKeys object and set to HTMLMediaElement then start the playback.
+  Promise.all([
+    LoadInitData(v, test, token),
+    CreateAndSetMediaKeys(v, test, token),
+    LoadTest(test, v, token)])
+  .then(values => {
+    let initData = values[0];
+    v.play();
+    initData.map(ev => {
+      let session = v.mediaKeys.createSession();
+      onSessionCreated(session);
+      MakeRequest(test, token, ev, session);
+    });
+  })
+  .then(() => {
+    return finish.promise;
+  })
+  .catch(reason => ok(false, reason))
+}
+
+function beginTest() {
+  manager.runTests(gEMETests, startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -9,16 +9,17 @@ support-files =
   nonTrickleIce.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
   sdpUtils.js
   addTurnsSelfsignedCert.js
+  parser_rtp.js
   !/dom/canvas/test/captureStream_common.js
   !/dom/canvas/test/webgl-mochitest/webgl-util.js
   !/dom/media/test/manifest.js
   !/dom/media/test/320x240.ogv
   !/dom/media/test/r11025_s16_c1.wav
   !/dom/media/test/bug461281.ogg
   !/dom/media/test/seek.webm
   !/dom/media/test/gizmo.mp4
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/parser_rtp.js
@@ -0,0 +1,131 @@
+/* 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";
+
+/*
+ * Parses an RTP packet
+ * @param buffer an ArrayBuffer that contains the packet
+ * @return { type: "rtp", header: {...}, payload: a DataView }
+ */
+var ParseRtpPacket = (buffer) => {
+
+  // DataView.getFooInt returns big endian numbers by default
+  let view = new DataView(buffer);
+
+  // Standard Header Fields
+  // https://tools.ietf.org/html/rfc3550#section-5.1
+  //  0                   1                   2                   3
+  //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  // |V=2|P|X|  CC   |M|     PT      |       sequence number         |
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  // |                           timestamp                           |
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+  // |           synchronization source (SSRC) identifier            |
+  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+  // |            contributing source (CSRC) identifiers             |
+  // |                             ....                              |
+  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+  let header = {};
+  let offset = 0;
+  // Note that incrementing the offset happens as close to reading the data as
+  // possible. This simplifies ensuring that the number of read bytes and the
+  // offset increment match. Data may be manipulated between when the offset is
+  // incremented and before the next read.
+  let byte = view.getUint8(offset);
+  offset++;
+  // Version            2 Bit
+  header.version = (0xC0 & byte) >> 6;
+  // Padding            1 Bit
+  header.padding = (0x30 & byte) >> 5
+  // Extension          1 Bit
+  header.extensionsPresent = ((0x10 & byte) >> 4) == 1;
+  // CSRC count         4 Bit
+  header.csrcCount = (0xF & byte);
+
+  byte = view.getUint8(offset);
+  offset++;
+  // Marker             1 Bit
+  header.marker =  (0x80 & byte) >> 7;
+  // Payload Type       7 Bit
+  header.payloadType = (0x7F & byte);
+  // Sequence Number   16 Bit
+  header.sequenceNumber = view.getUint16(offset);
+  offset += 2;
+  // Timestamp         32 Bit
+  header.timestamp = view.getUint32(offset);
+  offset += 4;
+  // SSRC              32 Bit
+  header.ssrc = view.getUint32(offset);
+  offset += 4;
+
+  // CSRC              32 Bit
+  header.csrcs = [];
+  for (let c = 0; c < header.csrcCount; c++) {
+    header.csrcs.push(view.getUint32(offset));
+    offset += 4;
+  }
+
+  // Extensions
+  header.extensions = [];
+  header.extensionPaddingBytes = 0;
+  header.extensionsTotalLength = 0;
+  if ( header.extensionsPresent ) {
+    // https://tools.ietf.org/html/rfc3550#section-5.3.1
+    //  0                   1                   2                   3
+    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    // |      defined by profile       |           length              |
+    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+    // |                        header extension                       |
+    // |                             ....                              |
+    let addExtension = (id, len) => header.extensions.push({
+        id: id,
+        data: new DataView(buffer, offset, len),
+    });
+    let extensionId = view.getUint16(offset);
+    offset += 2;
+    // len is in 32 bit units, not bytes
+    header.extensionsTotalLength = view.getUint16(offset) * 4;
+    offset += 2;
+    // Check for https://tools.ietf.org/html/rfc5285
+    if (extensionId != 0xBEDE) {
+      // No rfc5285
+      addExtension(extensionId, header.extensionsTotalLength);
+      offset += header.extensionsTotalLength;
+    } else {
+      let expectedEnd = offset + header.extensionsTotalLength;
+      while (offset < expectedEnd) {
+        // We only support "one-byte" extension headers ATM
+        // https://tools.ietf.org/html/rfc5285#section-4.2
+        //  0
+        //  0 1 2 3 4 5 6 7
+        // +-+-+-+-+-+-+-+-+
+        // |  ID   |  len  |
+        // +-+-+-+-+-+-+-+-+
+        byte = view.getUint8(offset);
+        offset++;
+        // Check for padding which can occur between extensions or at the end
+        if (byte == 0) {
+          header.extensionPaddingBytes++;
+          continue;
+        }
+        let id = (byte & 0xF0) >> 4;
+        // Check for the FORBIDDEN id (15), dun dun dun
+        if (id == 15) {
+          // Ignore bytes until until the end of extensions
+          offset = expectedEnd;
+          break;
+        }
+        // the length of the extention is len + 1
+        let len = (byte & 0x0F) + 1;
+        addExtension(id, len);
+        offset += len;
+      }
+    }
+  }
+  return { type: "rtp", header: header, payload: new DataView(buffer, offset) };
+}
\ No newline at end of file
--- a/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
@@ -1,12 +1,13 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="parser_rtp.js"></script>
   <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231507",
     title: "Basic video-only peer connection with Simulcast offer",
@@ -72,30 +73,58 @@
           info("Answer with RIDs: " + JSON.stringify(test._remote_answer));
           ok(test._remote_answer.sdp.match(/a=simulcast:/), "Modified answer has simulcast");
           ok(test._remote_answer.sdp.match(/a=rid:foo/), "Modified answer has rid foo");
           ok(test._remote_answer.sdp.match(/a=rid:bar/), "Modified answer has rid bar");
           ok(test._remote_answer.sdp.match(/urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id/), "Modified answer has RID");
         }
       ]);
 
+      // For storing the rid extension so it can be checked in the RTP
+      let ridExtensionId = 0;
+
       // do this after set local description so the MediaPipeline
       // has been created.
       test.chain.insertAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION',[
         function PC_REMOTE_SET_RTP_FIRST_RID(test) {
           const extmap_id = test.originalOffer.sdp.match(
               "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
           ok(extmap_id, "Original offer has extmap id for simulcast: " + extmap_id[1]);
+          ridExtensionId = extmap_id[1];
           // Cause pcRemote to filter out everything but RID "foo", only
           // allowing one of the simulcast streams through.
           addRIDExtension(test.pcRemote, extmap_id[1]);
           selectRecvRID(test.pcRemote, "foo");
         }
       ]);
 
+      let getRtpPacket = (pc) => {
+        pc.mozEnablePacketDump(0, "rtp", false);
+        return new Promise((res, rej) =>
+          pc.mozSetPacketCallback((...args) => {
+            res([...args]);
+            pc.mozSetPacketCallback(() => {});
+            pc.mozDisablePacketDump(0, "rtp", false);
+          })
+        );
+      }
+
+      test.chain.insertBefore('PC_REMOTE_WAIT_FOR_MEDIA_FLOW', [
+          async function PC_REMOTE_CHECK_RID_IN_RTP() {
+            let pc = SpecialPowers.wrap(test.pcRemote._pc);
+            let [level, type, sending, data] =  await getRtpPacket(pc);
+            let extensions = ParseRtpPacket(data).header.extensions;
+            ok(ridExtensionId, "RID extension ID has been extracted from SDP");
+            let ridExt = extensions.find(e => e.id == ridExtensionId);
+            ok(ridExt, "RID is present in RTP.");
+            is(new TextDecoder('utf-8').decode(ridExt.data), "foo",
+               "RID is 'foo'.");
+          }
+      ]);
+
       test.chain.append([
         async function PC_REMOTE_WAIT_FOR_FRAMES() {
           const vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
           emitter.start();
           await helper.checkVideoPlaying(vremote);
           emitter.stop();
         },
--- a/dom/media/webaudio/AudioEventTimeline.cpp
+++ b/dom/media/webaudio/AudioEventTimeline.cpp
@@ -80,16 +80,17 @@ AudioTimelineEvent::AudioTimelineEvent(M
   : mType(Stream)
   , mCurve(nullptr)
   , mStream(aStream)
   , mTimeConstant(0.0)
   , mDuration(0.0)
 #ifdef DEBUG
   , mTimeIsInTicks(false)
 #endif
+  , mTime(0.0)
 {
 }
 
 AudioTimelineEvent::AudioTimelineEvent(const AudioTimelineEvent& rhs)
 {
   PodCopy(this, &rhs, 1);
 
   if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -4356,18 +4356,22 @@ HTMLEditRules::WillOutdent(Selection& aS
     bool curBlockQuoteIsIndentedWithCSS = false;
     for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
       if (!arrayOfNodes[i]->IsContent()) {
         continue;
       }
       OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
 
       // Here's where we actually figure out what to do
-      nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
-      int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+      int32_t offset;
+      nsCOMPtr<nsINode> curParent =
+        EditorBase::GetNodeLocation(curNode, &offset);
+      if (!curParent) {
+        continue;
+      }
 
       // Is it a blockquote?
       if (curNode->IsHTMLElement(nsGkAtoms::blockquote)) {
         // If it is a blockquote, remove it.  So we need to finish up dealng
         // with any curBlockQuote first.
         if (curBlockQuote) {
           rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
                                   curBlockQuoteIsIndentedWithCSS,
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1414581.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function jsfuzzer() {
+  document.execCommand("outdent", false);
+}
+function eventhandler2() {
+  document.execCommand("styleWithCSS", false, true);
+}
+function eventhandler3() {
+  document.execCommand("delete", false);
+  var element1 = document.getElementById("element1");
+  document.getSelection().setPosition(element1, 0);
+  var element1 = document.getElementById("element2");
+  element2.ownerDocument.execCommand("insertOrderedList", false);
+  var element1 = document.getElementById("element3");
+  element3.addEventListener("DOMSubtreeModified", eventhandler3);
+  document.activeElement.setAttribute("contenteditable", "true");
+}
+</script>
+</head>
+<body onload=jsfuzzer()>
+<i id="element1">
+<audio i src="x"></audio>
+<da id="element2">
+<br id="element3" style="">
+<svg onload="eventhandler3()">
+<animate onbegin="eventhandler2()">
+<body>
+</html>
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -88,8 +88,9 @@ load 1383763.html
 load 1384161.html
 load 1388075.html
 load 1393171.html
 needs-focus load 1402196.html
 load 1402469.html
 load 1402904.html
 load 1405747.html
 load 1408170.html
+load 1414581.html
--- a/gfx/layers/wr/WebRenderLayersLogging.cpp
+++ b/gfx/layers/wr/WebRenderLayersLogging.cpp
@@ -70,10 +70,19 @@ AppendToString(std::stringstream& aStrea
     aStream << "ImageRendering::Pixelated"; break;
   case wr::ImageRendering::Sentinel:
     NS_ERROR("unknown texture filter");
     aStream << "???";
   }
   aStream << sfx;
 }
 
+void
+AppendToString(std::stringstream& aStream, wr::LayoutVector2D aVector,
+               const char* pfx, const char* sfx)
+{
+  aStream << pfx;
+  aStream << nsPrintfCString("(x=%f, y=%f)", aVector.x, aVector.y).get();
+  aStream << sfx;
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderLayersLogging.h
+++ b/gfx/layers/wr/WebRenderLayersLogging.h
@@ -16,15 +16,19 @@ namespace layers {
 void
 AppendToString(std::stringstream& aStream, wr::MixBlendMode aMixBlendMode,
                const char* pfx="", const char* sfx="");
 
 void
 AppendToString(std::stringstream& aStream, wr::ImageRendering aTextureFilter,
                const char* pfx="", const char* sfx="");
 
+void
+AppendToString(std::stringstream& aStream, wr::LayoutVector2D aVector,
+               const char* pfx="", const char* sfx="");
+
 } // namespace layers
 } // namespace mozilla
 
 // this ensures that the WebRender AppendToString's are in scope for Stringify
 #include "LayersLogging.h"
 
 #endif // GFX_WEBRENDERLAYERSLOGGING_H
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -806,29 +806,32 @@ DisplayListBuilder::GetCacheOverride(con
 
 wr::WrStickyId
 DisplayListBuilder::DefineStickyFrame(const wr::LayoutRect& aContentRect,
                                       const float* aTopMargin,
                                       const float* aRightMargin,
                                       const float* aBottomMargin,
                                       const float* aLeftMargin,
                                       const StickyOffsetBounds& aVerticalBounds,
-                                      const StickyOffsetBounds& aHorizontalBounds)
+                                      const StickyOffsetBounds& aHorizontalBounds,
+                                      const wr::LayoutVector2D& aAppliedOffset)
 {
   uint64_t id = wr_dp_define_sticky_frame(mWrState, aContentRect, aTopMargin,
-      aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds, aHorizontalBounds);
-  WRDL_LOG("DefineSticky id=%" PRIu64 " c=%s t=%s r=%s b=%s l=%s v=%s h=%s\n",
+      aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds, aHorizontalBounds,
+      aAppliedOffset);
+  WRDL_LOG("DefineSticky id=%" PRIu64 " c=%s t=%s r=%s b=%s l=%s v=%s h=%s a=%s\n",
       mWrState, id,
       Stringify(aContentRect).c_str(),
       aTopMargin ? Stringify(*aTopMargin).c_str() : "none",
       aRightMargin ? Stringify(*aRightMargin).c_str() : "none",
       aBottomMargin ? Stringify(*aBottomMargin).c_str() : "none",
       aLeftMargin ? Stringify(*aLeftMargin).c_str() : "none",
       Stringify(aVerticalBounds).c_str(),
-      Stringify(aHorizontalBounds).c_str());
+      Stringify(aHorizontalBounds).c_str(),
+      Stringify(aAppliedOffset).c_str());
   return wr::WrStickyId { id };
 }
 
 void
 DisplayListBuilder::PushStickyFrame(const wr::WrStickyId& aStickyId,
                                     const DisplayItemClipChain* aParent)
 {
   wr_dp_push_clip(mWrState, aStickyId.id);
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -242,17 +242,18 @@ public:
   Maybe<wr::WrClipId> GetCacheOverride(const DisplayItemClipChain* aParent);
 
   wr::WrStickyId DefineStickyFrame(const wr::LayoutRect& aContentRect,
                                    const float* aTopMargin,
                                    const float* aRightMargin,
                                    const float* aBottomMargin,
                                    const float* aLeftMargin,
                                    const StickyOffsetBounds& aVerticalBounds,
-                                   const StickyOffsetBounds& aHorizontalBounds);
+                                   const StickyOffsetBounds& aHorizontalBounds,
+                                   const wr::LayoutVector2D& aAppliedOffset);
   void PushStickyFrame(const wr::WrStickyId& aStickyId,
                        const DisplayItemClipChain* aParent);
   void PopStickyFrame(const DisplayItemClipChain* aParent);
 
   bool IsScrollLayerDefined(layers::FrameMetrics::ViewID aScrollId) const;
   void DefineScrollLayer(const layers::FrameMetrics::ViewID& aScrollId,
                          const Maybe<layers::FrameMetrics::ViewID>& aAncestorScrollId,
                          const Maybe<wr::WrClipId>& aAncestorClipId,
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1326,28 +1326,28 @@ pub extern "C" fn wr_dp_pop_clip(state: 
 #[no_mangle]
 pub extern "C" fn wr_dp_define_sticky_frame(state: &mut WrState,
                                             content_rect: LayoutRect,
                                             top_margin: *const f32,
                                             right_margin: *const f32,
                                             bottom_margin: *const f32,
                                             left_margin: *const f32,
                                             vertical_bounds: StickyOffsetBounds,
-                                            horizontal_bounds: StickyOffsetBounds)
+                                            horizontal_bounds: StickyOffsetBounds,
+                                            applied_offset: LayoutVector2D)
                                             -> u64 {
     assert!(unsafe { is_in_main_thread() });
     let clip_id = state.frame_builder.dl_builder.define_sticky_frame(
         None, content_rect, SideOffsets2D::new(
             unsafe { top_margin.as_ref() }.cloned(),
             unsafe { right_margin.as_ref() }.cloned(),
             unsafe { bottom_margin.as_ref() }.cloned(),
             unsafe { left_margin.as_ref() }.cloned()
         ),
-        vertical_bounds, horizontal_bounds,
-        LayoutVector2D::new(0.0, 0.0));
+        vertical_bounds, horizontal_bounds, applied_offset);
     match clip_id {
         ClipId::Clip(id, pipeline_id) => {
             assert!(pipeline_id == state.pipeline_id);
             id
         },
         _ => panic!("Got unexpected clip id type"),
     }
 }
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -541,16 +541,31 @@ struct StickyOffsetBounds {
   float max;
 
   bool operator==(const StickyOffsetBounds& aOther) const {
     return min == aOther.min &&
            max == aOther.max;
   }
 };
 
+// A 2d Vector tagged with a unit.
+struct TypedVector2D_f32__LayerPixel {
+  float x;
+  float y;
+
+  bool operator==(const TypedVector2D_f32__LayerPixel& aOther) const {
+    return x == aOther.x &&
+           y == aOther.y;
+  }
+};
+
+typedef TypedVector2D_f32__LayerPixel LayerVector2D;
+
+typedef LayerVector2D LayoutVector2D;
+
 struct BorderWidths {
   float left;
   float top;
   float right;
   float bottom;
 
   bool operator==(const BorderWidths& aOther) const {
     return left == aOther.left &&
@@ -621,31 +636,16 @@ struct NinePatchDescriptor {
 
   bool operator==(const NinePatchDescriptor& aOther) const {
     return width == aOther.width &&
            height == aOther.height &&
            slice == aOther.slice;
   }
 };
 
-// A 2d Vector tagged with a unit.
-struct TypedVector2D_f32__LayerPixel {
-  float x;
-  float y;
-
-  bool operator==(const TypedVector2D_f32__LayerPixel& aOther) const {
-    return x == aOther.x &&
-           y == aOther.y;
-  }
-};
-
-typedef TypedVector2D_f32__LayerPixel LayerVector2D;
-
-typedef LayerVector2D LayoutVector2D;
-
 struct Shadow {
   LayoutVector2D offset;
   ColorF color;
   float blur_radius;
 
   bool operator==(const Shadow& aOther) const {
     return offset == aOther.offset &&
            color == aOther.color &&
@@ -1073,17 +1073,18 @@ WR_FUNC;
 WR_INLINE
 uint64_t wr_dp_define_sticky_frame(WrState *aState,
                                    LayoutRect aContentRect,
                                    const float *aTopMargin,
                                    const float *aRightMargin,
                                    const float *aBottomMargin,
                                    const float *aLeftMargin,
                                    StickyOffsetBounds aVerticalBounds,
-                                   StickyOffsetBounds aHorizontalBounds)
+                                   StickyOffsetBounds aHorizontalBounds,
+                                   LayoutVector2D aAppliedOffset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_pop_all_shadows(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_pop_clip(WrState *aState)
--- a/ipc/chromium/moz.build
+++ b/ipc/chromium/moz.build
@@ -38,17 +38,16 @@ if os_win:
         'src/base/cpu.cc',
         'src/base/file_util_win.cc',
         'src/base/lock_impl_win.cc',
         'src/base/message_pump_win.cc',
         'src/base/object_watcher.cc',
         'src/base/platform_file_win.cc',
         'src/base/platform_thread_win.cc',
         'src/base/process_util_win.cc',
-        'src/base/process_win.cc',
         'src/base/rand_util_win.cc',
         'src/base/shared_memory_win.cc',
         'src/base/sys_info_win.cc',
         'src/base/sys_string_conversions_win.cc',
         'src/base/thread_local_storage_win.cc',
         'src/base/thread_local_win.cc',
         'src/base/time_win.cc',
         'src/base/waitable_event_win.cc',
@@ -65,17 +64,16 @@ if os_posix:
     UNIFIED_SOURCES += [
         'src/base/condition_variable_posix.cc',
         'src/base/file_descriptor_shuffle.cc',
         'src/base/file_util_posix.cc',
         'src/base/lock_impl_posix.cc',
         'src/base/message_pump_libevent.cc',
         'src/base/platform_file_posix.cc',
         'src/base/platform_thread_posix.cc',
-        'src/base/process_posix.cc',
         'src/base/process_util_posix.cc',
         'src/base/rand_util_posix.cc',
         'src/base/shared_memory_posix.cc',
         'src/base/string16.cc',
         'src/base/sys_info_posix.cc',
         'src/base/thread_local_posix.cc',
         'src/base/thread_local_storage_posix.cc',
         'src/base/waitable_event_posix.cc',
--- a/ipc/chromium/src/base/process.h
+++ b/ipc/chromium/src/base/process.h
@@ -23,59 +23,11 @@ namespace base {
 typedef HANDLE ProcessHandle;
 typedef DWORD ProcessId;
 #elif defined(OS_POSIX)
 // On POSIX, our ProcessHandle will just be the PID.
 typedef pid_t ProcessHandle;
 typedef pid_t ProcessId;
 #endif
 
-class Process {
- public:
-  Process() : process_(0), last_working_set_size_(0) {}
-  explicit Process(ProcessHandle aHandle) :
-    process_(aHandle), last_working_set_size_(0) {}
-
-  // A handle to the current process.
-  static Process Current();
-
-  // Get/Set the handle for this process. The handle will be 0 if the process
-  // is no longer running.
-  ProcessHandle handle() const { return process_; }
-  void set_handle(ProcessHandle aHandle) { process_ = aHandle; }
-
-  // Get the PID for this process.
-  ProcessId pid() const;
-
-  // Is the this process the current process.
-  bool is_current() const;
-
-  // Close the process handle. This will not terminate the process.
-  void Close();
-
-  // Terminates the process with extreme prejudice. The given result code will
-  // be the exit code of the process. If the process has already exited, this
-  // will do nothing.
-  void Terminate(int result_code);
-
-  // A process is backgrounded when it's priority is lower than normal.
-  // Return true if this process is backgrounded, false otherwise.
-  bool IsProcessBackgrounded() const;
-
-  // Set a prcess as backgrounded.  If value is true, the priority
-  // of the process will be lowered.  If value is false, the priority
-  // of the process will be made "normal" - equivalent to default
-  // process priority.
-  // Returns true if the priority was changed, false otherwise.
-  bool SetProcessBackgrounded(bool value);
-
-  // Releases as much of the working set back to the OS as possible.
-  // Returns true if successful, false otherwise.
-  bool EmptyWorkingSet();
-
- private:
-  ProcessHandle process_;
-  size_t last_working_set_size_;
-};
-
 }  // namespace base
 
 #endif  // BASE_PROCESS_H_
deleted file mode 100644
--- a/ipc/chromium/src/base/process_posix.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-// Copyright (c) 2008 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/process.h"
-#include "base/process_util.h"
-
-namespace base {
-
-void Process::Close() {
-  process_ = 0;
-  // if the process wasn't termiated (so we waited) or the state
-  // wasn't already collected w/ a wait from process_utils, we're gonna
-  // end up w/ a zombie when it does finally exit.
-}
-
-void Process::Terminate(int result_code) {
-  // result_code isn't supportable.
-  if (!process_)
-    return;
-  // We don't wait here. It's the responsibility of other code to reap the
-  // child.
-  KillProcess(process_, result_code, false);
-}
-
-bool Process::IsProcessBackgrounded() const {
-  // http://code.google.com/p/chromium/issues/detail?id=8083
-  return false;
-}
-
-bool Process::SetProcessBackgrounded(bool value) {
-  // http://code.google.com/p/chromium/issues/detail?id=8083
-  // Just say we did it to keep renderer happy at the moment.  Need to finish
-  // cleaning this up w/in higher layers since windows is probably the only
-  // one that can raise priorities w/o privileges.
-  return true;
-}
-
-bool Process::EmptyWorkingSet() {
-  // http://code.google.com/p/chromium/issues/detail?id=8083
-  return false;
-}
-
-ProcessId Process::pid() const {
-  if (process_ == 0)
-    return 0;
-
-  return GetProcId(process_);
-}
-
-bool Process::is_current() const {
-  return process_ == GetCurrentProcessHandle();
-}
-
-// static
-Process Process::Current() {
-  return Process(GetCurrentProcessHandle());
-}
-
-}  // namspace base
deleted file mode 100644
--- a/ipc/chromium/src/base/process_win.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/process.h"
-#include "base/logging.h"
-#include "base/process_util.h"
-
-namespace base {
-
-void Process::Close() {
-  if (!process_)
-    return;
-  CloseProcessHandle(process_);
-  process_ = NULL;
-}
-
-void Process::Terminate(int result_code) {
-  if (!process_)
-    return;
-  ::TerminateProcess(process_, result_code);
-}
-
-bool Process::IsProcessBackgrounded() const {
-  DCHECK(process_);
-  DWORD priority = GetPriorityClass(process_);
-  if (priority == 0)
-    return false;  // Failure case.
-  return priority == BELOW_NORMAL_PRIORITY_CLASS;
-}
-
-bool Process::SetProcessBackgrounded(bool value) {
-  DCHECK(process_);
-  DWORD priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS;
-  return (SetPriorityClass(process_, priority) != 0);
-}
-
-bool Process::EmptyWorkingSet() {
-  if (!process_)
-    return false;
-
-  BOOL rv = SetProcessWorkingSetSize(process_, -1, -1);
-  return rv == TRUE;
-}
-
-ProcessId Process::pid() const {
-  if (process_ == 0)
-    return 0;
-
-  return GetProcId(process_);
-}
-
-bool Process::is_current() const {
-  return process_ == GetCurrentProcess();
-}
-
-// static
-Process Process::Current() {
-  return Process(GetCurrentProcess());
-}
-
-}  // namespace base
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -623,17 +623,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
     return false;
   }
 
   base::ProcessHandle process = 0;
 
   // send the child the PID so that it can open a ProcessHandle back to us.
   // probably don't want to do this in the long run
   char pidstring[32];
-  SprintfLiteral(pidstring,"%d", base::Process::Current().pid());
+  SprintfLiteral(pidstring, "%d", base::GetCurrentProcId());
 
   const char* const childProcessType =
       XRE_ChildProcessTypeToString(mProcessType);
 
 //--------------------------------------------------
 #if defined(OS_POSIX)
   // For POSIX, we have to be extremely anal about *not* using
   // std::wstring in code compiled with Mozilla's -fshort-wchar
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -829,17 +829,17 @@ case "$target" in
 
 *-netbsd*)
     DSO_CFLAGS=''
     CFLAGS="$CFLAGS -Dunix"
     CXXFLAGS="$CXXFLAGS -Dunix"
     if $CC -E - -dM </dev/null | grep __ELF__ >/dev/null; then
         DSO_PIC_CFLAGS='-fPIC -DPIC'
         DSO_LDOPTS='-shared'
-	BIN_FLAGS='-Wl,--export-dynamic'
+        MOZ_PROGRAM_LDFLAGS="$MOZ_PROGRAM_LDFLAGS -Wl,--export-dynamic"
     else
     	DSO_PIC_CFLAGS='-fPIC -DPIC'
     	DSO_LDOPTS='-shared'
     fi
     # This will fail on a.out systems prior to 1.5.1_ALPHA.
     if test "$LIBRUNPATH"; then
 	DSO_LDOPTS="-Wl,-R$LIBRUNPATH $DSO_LDOPTS"
     fi
@@ -1954,17 +1954,16 @@ AC_SUBST(AR_EXTRACT)
 AC_SUBST(AS)
 AC_SUBST(ASFLAGS)
 AC_SUBST(AS_DASH_C_FLAG)
 AC_SUBST(RC)
 AC_SUBST(RCFLAGS)
 AC_SUBST(WINDRES)
 AC_SUBST(IMPLIB)
 AC_SUBST(FILTER)
-AC_SUBST(BIN_FLAGS)
 AC_SUBST_LIST(MOZ_DEBUG_LDFLAGS)
 AC_SUBST(WARNINGS_AS_ERRORS)
 AC_SUBST(LIBICONV)
 
 AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(INCREMENTAL_LINKER)
 
@@ -2037,17 +2036,16 @@ AC_SUBST(HOST_LDFLAGS)
 AC_SUBST_LIST(HOST_OPTIMIZE_FLAGS)
 AC_SUBST(HOST_AR)
 AC_SUBST(HOST_AR_FLAGS)
 AC_SUBST(HOST_RANLIB)
 AC_SUBST(HOST_BIN_SUFFIX)
 
 AC_SUBST(TARGET_XPCOM_ABI)
 
-AC_SUBST(WRAP_LDFLAGS)
 AC_SUBST(MKSHLIB)
 AC_SUBST(MKCSHLIB)
 AC_SUBST_LIST(DSO_CFLAGS)
 AC_SUBST_LIST(DSO_PIC_CFLAGS)
 AC_SUBST(DSO_LDOPTS)
 AC_SUBST(BIN_SUFFIX)
 AC_SUBST(USE_N32)
 AC_SUBST(CC_VERSION)
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -7067,42 +7067,35 @@ nsCSSFrameConstructor::GetInsertionPrevS
   } else {
     // If there is no previous sibling, then find the frame that follows
     //
     // FIXME(emilio): This is really complex and probably shouldn't be.
     if (aEndSkipChild) {
       iter.Seek(aEndSkipChild);
       iter.GetPreviousChild();
     }
-    nsIFrame* nextSibling = FindNextSibling(iter, childDisplay);
-    if (nextSibling) {
+    if (nsIFrame* nextSibling = FindNextSibling(iter, childDisplay)) {
       aInsertion->mParentFrame = nextSibling->GetParent()->GetContentInsertionFrame();
     } else {
       // No previous or next sibling, so treat this like an appended frame.
       *aIsAppend = true;
       if (IsFramePartOfIBSplit(aInsertion->mParentFrame)) {
         // Since we're appending, we'll walk to the last anonymous frame
         // that was created for the broken inline frame.  But don't walk
         // to the trailing inline if it's empty; stop at the block.
         aInsertion->mParentFrame =
           GetLastIBSplitSibling(aInsertion->mParentFrame, false);
       }
-      // Get continuation that parents the last child.  This MUST be done
-      // before the AdjustAppendParentForAfterContent call.
+      // Get continuation that parents the last child.
       aInsertion->mParentFrame =
         nsLayoutUtils::LastContinuationWithChild(aInsertion->mParentFrame);
       // Deal with fieldsets
       aInsertion->mParentFrame =
         ::GetAdjustedParentFrame(aInsertion->mParentFrame, aChild);
-      nsIFrame* appendAfterFrame;
-      aInsertion->mParentFrame =
-        ::AdjustAppendParentForAfterContent(this, aInsertion->mContainer,
-                                            aInsertion->mParentFrame,
-                                            aChild, &appendAfterFrame);
-      prevSibling = ::FindAppendPrevSibling(aInsertion->mParentFrame, appendAfterFrame);
+      prevSibling = ::FindAppendPrevSibling(aInsertion->mParentFrame, nullptr);
     }
   }
 
   *aIsRangeInsertSafe = (childDisplay == UNSET_DISPLAY);
   return prevSibling;
 }
 
 nsContainerFrame*
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -174,17 +174,17 @@ load 397852-1.xhtml
 load 398181-1.html
 load 398181-2.html
 load 398322-1.html
 load 398322-2.html
 load 398332-1.html
 load 398332-2.html
 asserts(0-2) load 398332-3.html # bug 436123 and bug 457397
 load 399407-1.xhtml
-load 399412-1.html
+asserts(1) load 399412-1.html # bug 574889
 load 399843-1.html
 load 400078-1.html
 load 400190.html
 load 400223-1.html # bug 1323652
 load 400232-1.html
 load 400244-1.html
 load 400768-1.xhtml
 load 400768-2.xhtml
@@ -570,17 +570,17 @@ load 973701-2.xhtml
 load 986899.html
 load 1001233.html
 load 1001258-1.html
 load 1001994.html
 load 1003441.xul
 pref(layout.css.grid.enabled,true) load 1015562.html
 asserts(1-2) load 1015563-1.html
 asserts(1-2) load 1015563-2.html
-asserts(543) asserts-if(stylo&&Android,274) load 1015844.html # bug 574889, bug 1374479
+asserts(11) asserts-if(stylo&&Android,274) load 1015844.html # bug 574889, bug 1374479
 pref(font.size.inflation.minTwips,200) load 1032450.html
 load 1032613-1.svg
 load 1032613-2.html
 load 1037903.html
 load 1039454-1.html
 load 1042489.html
 load 1054010-1.html
 load 1058954-1.html
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -7487,20 +7487,19 @@ nsBlockFrame::ComputeFinalBSize(const Re
                "overflow container must not have computedBSizeLeftOver");
 
   aFinalSize.BSize(wm) =
     NSCoordSaturatingAdd(NSCoordSaturatingAdd(aBorderPadding.BStart(wm),
                                               computedBSizeLeftOver),
                          aBorderPadding.BEnd(wm));
 
   if (aStatus->IsIncomplete() &&
-      aFinalSize.BSize(wm) < aReflowInput.AvailableBSize()) {
-    // We fit in the available space - change status to OVERFLOW_INCOMPLETE.
-    // XXXmats why didn't Reflow report OVERFLOW_INCOMPLETE in the first place?
-    // XXXmats and why exclude the case when our size == AvailableBSize?
+      aFinalSize.BSize(wm) <= aReflowInput.AvailableBSize()) {
+    // We ran out of height on this page but we're incomplete.
+    // Set status to complete except for overflow.
     aStatus->SetOverflowIncomplete();
   }
 
   if (aStatus->IsComplete()) {
     if (computedBSizeLeftOver > 0 &&
         NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
         aFinalSize.BSize(wm) > aReflowInput.AvailableBSize()) {
       if (ShouldAvoidBreakInside(aReflowInput)) {
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7313,27 +7313,45 @@ bool
 nsDisplayStickyPosition::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                                  mozilla::wr::IpcResourceUpdateQueue& aResources,
                                                  const StackingContextHelper& aSc,
                                                  WebRenderLayerManager* aManager,
                                                  nsDisplayListBuilder* aDisplayListBuilder)
 {
   StickyScrollContainer* stickyScrollContainer = StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame);
   if (stickyScrollContainer) {
+    // If there's no ASR for the scrollframe that this sticky item is attached
+    // to, then don't create a WR sticky item for it either. Trying to do so
+    // will end in sadness because WR will interpret some coordinates as
+    // relative to the nearest enclosing scrollframe, which will correspond
+    // to the nearest ancestor ASR on the gecko side. That ASR will not be the
+    // same as the scrollframe this sticky item is actually supposed to be
+    // attached to, thus the sadness.
+    // Not sending WR the sticky item is ok, because the enclosing scrollframe
+    // will never be asynchronously scrolled. Instead we will always position
+    // the sticky items correctly on the gecko side and WR will never need to
+    // adjust their position itself.
+    if (!stickyScrollContainer->ScrollFrame()->MayBeAsynchronouslyScrolled()) {
+      stickyScrollContainer = nullptr;
+    }
+  }
+
+  if (stickyScrollContainer) {
     float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
 
     bool snap;
     nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap);
 
     Maybe<float> topMargin;
     Maybe<float> rightMargin;
     Maybe<float> bottomMargin;
     Maybe<float> leftMargin;
     wr::StickyOffsetBounds vBounds = { 0.0, 0.0 };
     wr::StickyOffsetBounds hBounds = { 0.0, 0.0 };
+    nsPoint appliedOffset;
 
     nsRect outer;
     nsRect inner;
     stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);
 
     nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame());
     nsPoint offset = scrollFrame->GetOffsetToCrossDoc(ReferenceFrame());
 
@@ -7359,54 +7377,80 @@ nsDisplayStickyPosition::CreateWebRender
       // top of the scrollport. So in that case the adjustment is -distance.
       // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then
       // we would be scrolling downwards, itemBounds.y would decrease, and we
       // again need to adjust by -distance. If we are already in the range
       // then no adjustment is needed and distance is 0 so again using
       // -distance works.
       nscoord distance = DistanceToRange(inner.YMost(), outer.YMost());
       topMargin = Some(NSAppUnitsToFloatPixels(itemBounds.y - scrollPort.y - distance, auPerDevPixel));
-      // Question: Given the current state, what is the range during which
-      // WR will have to apply an adjustment to the item (in order to prevent
-      // the item from visually moving) as a result of async scrolling?
-      // Answer: [inner.YMost(), outer.YMost()]. But right now the WR API
-      // doesn't allow us to provide the whole range; it just takes one side
-      // of the range and assumes it has a particular sign. Bug 1411627 will
-      // fix this more completely but for now we do the best we can. Note that
-      // this value also needs to be converted from being relative to the
-      // scrollframe to being relative to the reference frame, so we have to
-      // adjust it by |offset|.
-      vBounds.max = NSAppUnitsToFloatPixels(std::max(0, outer.YMost() + offset.y), auPerDevPixel);
+      // Question: What is the maximum positive ("downward") offset that WR
+      // will have to apply to this item in order to prevent the item from
+      // visually moving?
+      // Answer: Since the item is "sticky" in the range [inner.YMost(), outer.YMost()],
+      // the maximum offset will be the size of the range, which is
+      // outer.YMost() - inner.YMost().
+      vBounds.max = NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel);
+      // Question: how much of an offset has layout already applied to the item?
+      // Answer: if we are
+      // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or
+      // (b) past the sticky range (inner.YMost() < outer.YMost() < 0)
+      // then layout has already applied some offset to the position of the
+      // item. The amount of the adjustment is |0 - inner.YMost()| in case (a)
+      // and |outer.YMost() - inner.YMost()| in case (b).
+      if (inner.YMost() < 0) {
+        appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost();
+        MOZ_ASSERT(appliedOffset.y > 0);
+      }
     }
     if (outer.y != inner.y) {
       // Similar logic as in the previous section, but this time we care about
       // the distance from itemBounds.YMost() to scrollPort.YMost().
       nscoord distance = DistanceToRange(outer.y, inner.y);
       bottomMargin = Some(NSAppUnitsToFloatPixels(scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel));
       // And here WR will be moving the item upwards rather than downwards so
       // again things are inverted from the previous block.
-      vBounds.min = NSAppUnitsToFloatPixels(std::min(0, outer.y + offset.y), auPerDevPixel);
+      vBounds.min = NSAppUnitsToFloatPixels(outer.y - inner.y, auPerDevPixel);
+      // We can't have appliedOffset be both positive and negative, and the top
+      // adjustment takes priority. So here we only update appliedOffset.y if
+      // it wasn't set by the top-sticky case above.
+      if (appliedOffset.y == 0 && inner.y > 0) {
+        appliedOffset.y = std::max(0, outer.y) - inner.y;
+        MOZ_ASSERT(appliedOffset.y < 0);
+      }
     }
     // Same as above, but for the x-axis
     if (outer.XMost() != inner.XMost()) {
       nscoord distance = DistanceToRange(inner.XMost(), outer.XMost());
       leftMargin = Some(NSAppUnitsToFloatPixels(itemBounds.x - scrollPort.x - distance, auPerDevPixel));
-      hBounds.max = NSAppUnitsToFloatPixels(std::max(0, outer.XMost() + offset.x), auPerDevPixel);
+      hBounds.max = NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel);
+      if (inner.XMost() < 0) {
+        appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost();
+        MOZ_ASSERT(appliedOffset.x > 0);
+      }
     }
     if (outer.x != inner.x) {
       nscoord distance = DistanceToRange(outer.x, inner.x);
       rightMargin = Some(NSAppUnitsToFloatPixels(scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel));
-      hBounds.min = NSAppUnitsToFloatPixels(std::min(0, outer.x + offset.x), auPerDevPixel);
+      hBounds.min = NSAppUnitsToFloatPixels(outer.x - inner.x, auPerDevPixel);
+      if (appliedOffset.x == 0 && inner.x > 0) {
+        appliedOffset.x = std::max(0, outer.x) - inner.x;
+        MOZ_ASSERT(appliedOffset.x < 0);
+      }
     }
 
     LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel);
+    wr::LayoutVector2D applied = {
+      NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel),
+      NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)
+    };
     wr::WrStickyId id = aBuilder.DefineStickyFrame(aSc.ToRelativeLayoutRect(bounds),
         topMargin.ptrOr(nullptr), rightMargin.ptrOr(nullptr),
         bottomMargin.ptrOr(nullptr), leftMargin.ptrOr(nullptr),
-        vBounds, hBounds);
+        vBounds, hBounds, applied);
 
     aBuilder.PushStickyFrame(id, GetClipChain());
   }
 
   nsDisplayOwnLayer::CreateWebRenderCommands(aBuilder, aResources, aSc,
       aManager, aDisplayListBuilder);
 
   if (stickyScrollContainer) {
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -23,16 +23,19 @@ skip-if(!asyncPan) == position-fixed-cov
 skip-if(!asyncPan) == position-fixed-cover-3.html position-fixed-cover-3-ref.html
 fuzzy-if(Android,5,4) skip-if(!asyncPan) == position-fixed-transformed-1.html position-fixed-transformed-1-ref.html
 skip-if(!asyncPan) == split-layers-1.html split-layers-1-ref.html
 skip-if(!asyncPan) == split-layers-multi-scrolling-1.html split-layers-multi-scrolling-1-ref.html
 fuzzy-if(skiaContent,2,240000) fuzzy-if(browserIsRemote&&!skiaContent&&(cocoaWidget||winWidget),1,240000) skip-if(!asyncPan) == split-opacity-layers-1.html split-opacity-layers-1-ref.html
 skip-if(!asyncPan) == sticky-pos-scrollable-1.html sticky-pos-scrollable-1-ref.html
 skip-if(!asyncPan) == sticky-pos-scrollable-2.html sticky-pos-scrollable-2-ref.html
 skip-if(!asyncPan) == sticky-pos-scrollable-3.html sticky-pos-scrollable-3-ref.html
+skip-if(!asyncPan) == sticky-pos-scrollable-4.html sticky-pos-scrollable-4-ref.html
+skip-if(!asyncPan) == sticky-pos-scrollable-5.html sticky-pos-scrollable-5-ref.html
+skip-if(!asyncPan) == sticky-pos-scrollable-6.html sticky-pos-scrollable-6-ref.html
 skip-if(!asyncPan) == fixed-pos-scrollable-1.html fixed-pos-scrollable-1-ref.html
 skip-if(!asyncPan) == culling-1.html culling-1-ref.html
 skip-if(!asyncPan) == position-fixed-iframe-1.html position-fixed-iframe-1-ref.html
 skip-if(!asyncPan) == position-fixed-iframe-2.html position-fixed-iframe-2-ref.html
 fuzzy-if(skiaContent,1,11300) skip-if(!asyncPan) == position-fixed-in-scroll-container.html position-fixed-in-scroll-container-ref.html
 skip-if(!asyncPan) == position-fixed-inside-clip-path.html position-fixed-inside-clip-path-ref.html
 skip-if(!asyncPan) == position-fixed-inside-sticky-1.html position-fixed-inside-sticky-1-ref.html
 skip-if(!asyncPan) == position-fixed-inside-sticky-2.html position-fixed-inside-sticky-2-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/sticky-pos-scrollable-4-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div style="width:400px; height:300px; overflow:hidden; border:2px solid black">
+    <div style="height: 150px"></div>
+    <div style="width: 100px; height: 100px; background-color: green"></div>
+  </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/sticky-pos-scrollable-4.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<body>
+  <div style="width:400px; height:300px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="100">
+    <!-- In this test the position:sticky element has already been adjusted by
+         the main thread paint, and the async scroll causes a reduction in that
+         adjustment. -->
+    <div style="height: 1000px"></div>
+    <div style="position:sticky; width: 100px; height: 100px; bottom: 50px; background-color: green"></div>
+    <div style="height: 1000px"></div>
+  </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/sticky-pos-scrollable-5-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div style="width:400px; height:300px; overflow:hidden; border:2px solid black">
+    <div style="height: 150px"></div>
+    <div style="width: 100px; height: 100px; background-color: green"></div>
+  </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/sticky-pos-scrollable-5.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<body>
+  <div style="width:400px; height:300px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="150">
+    <!-- In this test the position:sticky element has already been adjusted by
+         the main thread paint, and then clamped by the bounding container. The
+         async scroll causes a reduction in that adjustment, but by less than
+         the amount of the scroll (because of the clamping). -->
+    <div style="height: 500px; margin-top: 250px; margin-bottom: 250px">
+      <div style="width: 100px; height: 300px"></div>
+      <div style="position:sticky; width: 100px; height: 100px; bottom: 50px; background-color: green"></div>
+    </div>
+  </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/sticky-pos-scrollable-6-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div style="width:400px; height:300px; overflow:hidden; border:2px solid black">
+    <div style="height: 151px"></div>
+    <div style="width: 100px; height: 100px; background-color: green"></div>
+  </div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/async-scrolling/sticky-pos-scrollable-6.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll>
+<body>
+  <div style="width:400px; height:300px; overflow:hidden; border:2px solid black"
+       reftest-displayport-x="0" reftest-displayport-y="0"
+       reftest-displayport-w="800" reftest-displayport-h="2000"
+       reftest-async-scroll-x="0" reftest-async-scroll-y="300">
+    <!-- In this test the position:sticky element is over-constrained with the
+         top-positioning taking priority over the bottom-positioning. -->
+    <div style="height: 300px"></div>
+    <div style="position:sticky; width: 100px; height: 100px; top: 151px; bottom: 50px; background-color: green"></div>
+    <div style="height: 1000px"></div>
+  </div>
--- a/layout/reftests/css-grid/grid-fragmentation-006-ref.html
+++ b/layout/reftests/css-grid/grid-fragmentation-006-ref.html
@@ -63,22 +63,24 @@ i { display:block; height:10px; margin-t
 
 <div class="columns" style="height: 100px/*item will be INCOMPLETE*/">
 <div style="background:grey">
 <div class="grid">
 <span style="grid-row:span 2"><i></i><i></i><i></i><i></i><i></i><i></i><x></x></span>
 </div>
 </div></div>
 
+<!-- bug 1415301
 <div class="columns" style="height: 140px/*trigger IsInlineBreakBefore() for the item*/">
 <div style="padding-top:30px; background:grey">
 <div class="grid">
 <span style="grid-row:span 3"><x></x></span>
 <span style="grid-row:span 2"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><x></x></span></div>
 </div></div>
+-->
 
 <div class="columns" style="height: 100px/*trigger IsInlineBreakBefore() for the item*/">
 <div style="padding-top:1px; background:grey">
 <div class="grid t" style="grid-auto-rows: 50px; height:50px;">
 <span class="t" style="grid-row:span 2; height:49px; overflow:hidden"><i></i><i></i><i></i><x></x></span>
 </div>
 <div style="height:44px"></div>
 <div class="grid b" style="grid-auto-rows: 50px; height:50px;">
--- a/layout/reftests/css-grid/grid-fragmentation-006.html
+++ b/layout/reftests/css-grid/grid-fragmentation-006.html
@@ -62,22 +62,24 @@ i { display:block; height:10px; margin-t
 
 <div class="columns" style="height: 100px/*item will be INCOMPLETE*/">
 <div style="background:grey">
 <div class="grid">
 <span class="avoid-break" style="grid-row:span 2"><i></i><i></i><i></i><i></i><i></i><i></i><x></x></span>
 </div>
 </div></div>
 
+<!-- bug 1415301
 <div class="columns" style="height: 140px/*trigger IsInlineBreakBefore() for the item*/">
 <div style="padding-top:30px; background:grey">
 <div class="grid">
 <span style="grid-row:span 3"><x></x></span>
 <span class="avoid-break" style="grid-row:span 2"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><x></x></span></div>
 </div></div>
+-->
 
 <div class="columns" style="height: 100px/*trigger IsInlineBreakBefore() for the item*/">
 <div style="padding-top:1px; background:grey">
 <div class="grid" style="grid-auto-rows: 50px;">
 <span class="avoid-break" style="grid-row:span 2; height:60px"><i></i><i></i><i></i><x></x></span>
 </div>
 </div></div>
 
--- a/layout/reftests/css-grid/grid-fragmentation-019-ref.html
+++ b/layout/reftests/css-grid/grid-fragmentation-019-ref.html
@@ -137,14 +137,13 @@ i { display:block; height:10px; margin-t
 </div>
 <div class="grid" style="grid-template-rows: 1fr; grid-gap:0; height:62px">
 <span style="height:60px"><x></x></span>
 <span style="height:50px"><i style="height:54px"></i><x></x></span>
 <span style="height:60px"><i></i><i></i><i></i><x></x></span>
 </div>
 <div class="grid" style="grid-template-rows: 0; grid-gap:0; border-width:0 5px">
 <span class=m style="grid-column:2; border-width:0 1px; height:0"><i style="height:6px;margin:0"></i></span>
-<span class=b style="grid-column:3; height:0"></span>
 </div>
 </div></div>
 
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/break3/moz-block-fragmentation-001-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<title>
+  Reference
+</title>
+<style>
+  * {
+    box-sizing: border-box;
+    margin: 0;
+    padding: 0;
+    position: absolute;
+    left: 0; right: 0;
+    height: 100%;
+  }
+  div {
+    border: solid orange 10px;
+  }
+  div + div {
+    border: solid transparent 20px;
+  }
+  div > div {
+    border: solid gray 10px;
+    height: 300%;
+  }
+</style>
+<div></div>
+<div>
+  <div></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/break3/moz-block-fragmentation-001.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<title>
+  Overflowing content does not affect whether a fixed-height box fits on a page,
+  but does get printed on the next page.
+</title>
+<meta name="flags" content="may print">
+<link rel="match" href="moz-block-fragmentation-001-ref.html">
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#parallel-flows">
+<style>
+  * {
+    box-sizing: border-box;
+    margin: 0;
+    padding: 0;
+    height: 100%;
+  }
+  body {
+    border: solid orange 10px;
+    padding: 10px;
+  }
+  div {
+    border: solid gray 10px;
+    height: 300%;
+  }
+</style>
+<div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/break3/reftest.list
@@ -0,0 +1,1 @@
+== moz-block-fragmentation-001.html moz-block-fragmentation-001-ref.html
--- a/layout/reftests/w3c-css/submitted/reftest.list
+++ b/layout/reftests/w3c-css/submitted/reftest.list
@@ -11,16 +11,19 @@
 include align3/reftest.list
 
 # CSS2.1
 include css21/reftest.list
 
 # Backgrounds and Borders
 include background/reftest.list
 
+# Fragmentation
+include break3/reftest.list
+
 # Color Level 4
 include color4/reftest.list
 
 # Conditional Rules Level 3
 include conditional3/reftest.list
 
 # Containment
 include contain/reftest.list
--- a/media/ffvpx/config.h
+++ b/media/ffvpx/config.h
@@ -2,17 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 MOZ_FFVPX_CONFIG_H
 #define MOZ_FFVPX_CONFIG_H
 #if defined(MOZ_FFVPX_FLACONLY)
-#if defined(MOZ_WIDGET_ANDROID) && !defined(HAVE_64BIT_BUILD)
+#if defined(MOZ_WIDGET_ANDROID) && defined(__arm__)
 #include "config_android32.h"
 #else
 #include "config_flac.h"
 #endif
 #else // MOZ_FFVPX_FLACONLY
 #if defined(XP_WIN)
 // Avoid conflicts with mozilla-config.h
 #if !defined(_MSC_VER)
--- a/media/ffvpx/libavcodec/avcodec.symbols
+++ b/media/ffvpx/libavcodec/avcodec.symbols
@@ -1,12 +1,8 @@
-av_bitstream_filter_close
-av_bitstream_filter_filter
-av_bitstream_filter_init
-av_bitstream_filter_next
 av_codec_ffversion
 av_codec_get_chroma_intra_matrix
 av_codec_get_codec_descriptor
 av_codec_get_codec_properties
 av_codec_get_lowres
 av_codec_get_max_lowres
 av_codec_get_pkt_timebase
 av_codec_get_seek_preroll
@@ -52,27 +48,18 @@ av_packet_side_data_name
 av_packet_split_side_data
 av_packet_unpack_dictionary
 av_packet_unref
 av_parser_change
 av_parser_close
 av_parser_init
 av_parser_next
 av_parser_parse2
-av_picture_copy
-av_picture_crop
-av_picture_pad
-av_qsv_alloc_context
-av_register_bitstream_filter
 av_register_codec_parser
 av_register_hwaccel
-av_resample
-av_resample_close
-av_resample_compensate
-av_resample_init
 av_shrink_packet
 av_vorbis_parse_frame
 av_vorbis_parse_frame_flags
 av_vorbis_parse_free
 av_vorbis_parse_init
 av_vorbis_parse_reset
 av_xiphlacing
 avcodec_align_dimensions
@@ -89,41 +76,30 @@ avcodec_default_execute
 avcodec_default_execute2
 avcodec_default_get_buffer2
 avcodec_default_get_format
 avcodec_descriptor_get
 avcodec_descriptor_get_by_name
 avcodec_descriptor_next
 avcodec_enum_to_chroma_pos
 avcodec_fill_audio_frame
-avcodec_find_best_pix_fmt2
-avcodec_find_best_pix_fmt_of_2
-avcodec_find_best_pix_fmt_of_list
 avcodec_find_decoder
 avcodec_find_decoder_by_name
 avcodec_find_encoder
 avcodec_find_encoder_by_name
 avcodec_flush_buffers
 avcodec_free_context
-avcodec_get_chroma_sub_sample
 avcodec_get_class
 avcodec_get_context_defaults3
 avcodec_get_edge_width
 avcodec_get_frame_class
 avcodec_get_name
-avcodec_get_pix_fmt_loss
 avcodec_get_subtitle_rect_class
 avcodec_get_type
 avcodec_is_open
 avcodec_license
 avcodec_open2
-avcodec_pix_fmt_to_codec_tag
 avcodec_register
 avcodec_register_all
 avcodec_set_dimensions
 avcodec_string
 avcodec_version
-avpicture_alloc
-avpicture_fill
-avpicture_free
-avpicture_get_size
-avpicture_layout
 avsubtitle_free
--- a/media/ffvpx/libavutil/avutil.symbols
+++ b/media/ffvpx/libavutil/avutil.symbols
@@ -1,15 +1,12 @@
 av_add_q
 av_add_stable
-av_adler32_update
 av_append_path_component
 av_asprintf
-av_base64_decode
-av_base64_encode
 av_basename
 av_bprint_append_data
 av_bprint_channel_layout
 av_bprint_chars
 av_bprint_clear
 av_bprint_escape
 av_bprint_finalize
 av_bprint_get_buffer
@@ -172,18 +169,20 @@ av_image_copy_to_buffer
 av_image_fill_arrays
 av_image_fill_linesizes
 av_image_fill_max_pixsteps
 av_image_fill_pointers
 av_image_get_buffer_size
 av_image_get_linesize
 av_int_list_length_for_size
 av_log
+#ifndef MOZ_FFVPX_FLACONLY
 av_log2
 av_log2_16bit
+#endif
 av_log_default_callback
 av_log_format_line
 av_log_get_flags
 av_log_get_level
 av_log_set_callback
 av_log_set_flags
 av_log_set_level
 av_malloc
@@ -252,17 +251,16 @@ av_parse_time
 av_parse_video_rate
 av_parse_video_size
 av_pix_fmt_count_planes
 av_pix_fmt_desc_get
 av_pix_fmt_desc_get_id
 av_pix_fmt_desc_next
 av_pix_fmt_get_chroma_sub_sample
 av_pix_fmt_swap_endianness
-av_pixelutils_get_sad_fn
 av_q2intfloat
 av_read_image_line
 av_realloc
 av_realloc_array
 av_realloc_f
 av_reallocp
 av_reallocp_array
 av_reduce
@@ -291,48 +289,31 @@ av_strlcatf
 av_strlcpy
 av_strncasecmp
 av_strndup
 av_strnstr
 av_strstart
 av_strtod
 av_strtok
 av_sub_q
-av_thread_message_queue_alloc
-av_thread_message_queue_free
-av_thread_message_queue_recv
-av_thread_message_queue_send
-av_thread_message_queue_set_err_recv
-av_thread_message_queue_set_err_send
-av_timecode_adjust_ntsc_framenum2
-av_timecode_check_frame_rate
-av_timecode_get_smpte_from_framenum
-av_timecode_init
-av_timecode_init_from_string
-av_timecode_make_mpeg_tc_string
-av_timecode_make_smpte_tc_string
-av_timecode_make_string
 av_timegm
 av_usleep
 av_utf8_decode
 av_util_ffversion
 av_vbprintf
 av_version_info
 av_vlog
 av_write_image_line
 avpriv_alloc_fixed_dsp
 avpriv_float_dsp_alloc
 avpriv_frame_get_metadatap
-avpriv_get_gamma_from_trc
-avpriv_init_lls
 avpriv_report_missing_feature
 avpriv_request_sample
 avpriv_scalarproduct_float_c
 avpriv_set_systematic_pal2
-avpriv_solve_lls
 avutil_configuration
 avutil_license
 avutil_version
 #ifdef XP_WIN
 avpriv_emms_asm
 #endif
 avpriv_slicethread_create
 avpriv_slicethread_execute
--- a/media/libstagefright/binding/mp4parse/src/boxes.rs
+++ b/media/libstagefright/binding/mp4parse/src/boxes.rs
@@ -132,9 +132,11 @@ box_database!(
     QTWaveAtom                        0x77617665, // "wave" - quicktime atom
     ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh"
     SchemeInformationBox              0x73636869, // "schi"
     TrackEncryptionBox                0x74656e63, // "tenc"
     ProtectionSchemeInformationBox    0x73696e66, // "sinf"
     OriginalFormatBox                 0x66726d61, // "frma"
     MP3AudioSampleEntry               0x2e6d7033, // ".mp3" - from F4V.
     CompositionOffsetBox              0x63747473, // "ctts"
+    AudioChannelLayoutAtom            0x6368616E, // "chan" - quicktime atom
+    LPCMAudioSampleEntry              0x6C70636D, // "lpcm" - quicktime atom
 );
--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -314,24 +314,25 @@ pub struct ES_Descriptor {
 
 #[allow(non_camel_case_types)]
 #[derive(Debug, Clone)]
 pub enum AudioCodecSpecific {
     ES_Descriptor(ES_Descriptor),
     FLACSpecificBox(FLACSpecificBox),
     OpusSpecificBox(OpusSpecificBox),
     MP3,
+    LPCM,
 }
 
 #[derive(Debug, Clone)]
 pub struct AudioSampleEntry {
     data_reference_index: u16,
-    pub channelcount: u16,
+    pub channelcount: u32,
     pub samplesize: u16,
-    pub samplerate: u32,
+    pub samplerate: f64,
     pub codec_specific: AudioCodecSpecific,
     pub protection_info: Vec<ProtectionSchemeInfoBox>,
 }
 
 #[derive(Debug, Clone)]
 pub enum VideoCodecSpecific {
     AVCConfig(Vec<u8>),
     VPxConfig(VPxConfigBox),