Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 08 Mar 2017 10:08:57 -0500
changeset 347280 335eb4955c12399374249d82de8180425dcc7f83
parent 347279 6374644ff317f40c9ddb43483fc70c45a121021b (current diff)
parent 346456 becff35a0bed14b536bb0a141b0e9640e9cb063d (diff)
child 347281 1ff6805a848268bd2ab84c27b59fa35f93f07bef
push id31493
push userkwierso@gmail.com
push dateMon, 13 Mar 2017 20:10:14 +0000
treeherdermozilla-central@7781e4e0a332 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to graphics MozReview-Commit-ID: AzRIytAgP96
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
layout/painting/nsDisplayList.cpp
services/sync/tests/unit/test_service_migratePrefs.js
taskcluster/docker/desktop1604-test/deja-dup-monitor.desktop
taskcluster/docker/desktop1604-test/jockey-gtk.desktop
third_party/rust/bitreader/LICENSE
toolkit/components/search/tests/xpcshell/data/engine-update.xml
toolkit/components/search/tests/xpcshell/test_update_telemetry.js
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -6,16 +6,17 @@ module.exports = {
     "mozilla"
   ],
   "rules": {
     "mozilla/avoid-removeChild": "error",
     "mozilla/import-globals": "warn",
     "mozilla/no-import-into-var-and-global": "error",
     "mozilla/no-useless-parameters": "error",
     "mozilla/no-useless-removeEventListener": "error",
+    "mozilla/use-default-preference-values": "error",
     "mozilla/use-ownerGlobal": "error",
 
     // No (!foo in bar) or (!object instanceof Class)
     "no-unsafe-negation": "error",
   },
   "env": {
     "es6": true
   },
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -10,16 +10,20 @@
 
 const {utils: Cu, interfaces: Ci} = Components;
 
 this.EXPORTED_SYMBOLS = ['AccessFu']; // jshint ignore:line
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 
+if (Utils.MozBuildApp === 'mobile/android') {
+  Cu.import('resource://gre/modules/Messaging.jsm');
+}
+
 const ACCESSFU_DISABLE = 0; // jshint ignore:line
 const ACCESSFU_ENABLE = 1;
 const ACCESSFU_AUTO = 2;
 
 const SCREENREADER_SETTING = 'accessibility.screenreader';
 const QUICKNAV_MODES_PREF = 'accessibility.accessfu.quicknav_modes';
 const QUICKNAV_INDEX_PREF = 'accessibility.accessfu.quicknav_index';
 
@@ -27,21 +31,19 @@ this.AccessFu = { // jshint ignore:line
   /**
    * Initialize chrome-layer accessibility functionality.
    * If accessibility is enabled on the platform, then a special accessibility
    * mode is started.
    */
   attach: function attach(aWindow) {
     Utils.init(aWindow);
 
-    try {
-      Services.androidBridge.dispatch('Accessibility:Ready');
-      Services.obs.addObserver(this, 'Accessibility:Settings', false);
-    } catch (x) {
-      // Not on Android
+    if (Utils.MozBuildApp === 'mobile/android') {
+      EventDispatcher.instance.dispatch('Accessibility:Ready');
+      EventDispatcher.instance.registerListener(this, 'Accessibility:Settings');
     }
 
     this._activatePref = new PrefCache(
       'accessibility.accessfu.activate', this._enableOrDisable.bind(this));
 
     this._enableOrDisable();
   },
 
@@ -49,17 +51,17 @@ this.AccessFu = { // jshint ignore:line
    * Shut down chrome-layer accessibility functionality from the outside.
    */
   detach: function detach() {
     // Avoid disabling twice.
     if (this._enabled) {
       this._disable();
     }
     if (Utils.MozBuildApp === 'mobile/android') {
-      Services.obs.removeObserver(this, 'Accessibility:Settings');
+      EventDispatcher.instance.unregisterListener(this, 'Accessibility:Settings');
     }
     delete this._activatePref;
     Utils.uninit();
   },
 
   /**
    * A lazy getter for event handler that binds the scope to AccessFu object.
    */
@@ -115,26 +117,31 @@ this.AccessFu = { // jshint ignore:line
     this._notifyOutputPref =
       new PrefCache('accessibility.accessfu.notify_output');
 
 
     this.Input.start();
     Output.start();
     PointerAdapter.start();
 
+    if (Utils.MozBuildApp === 'mobile/android') {
+      EventDispatcher.instance.registerListener(this, [
+        'Accessibility:ActivateObject',
+        'Accessibility:Focus',
+        'Accessibility:LongPress',
+        'Accessibility:MoveByGranularity',
+        'Accessibility:NextObject',
+        'Accessibility:PreviousObject',
+        'Accessibility:ScrollBackward',
+        'Accessibility:ScrollForward',
+      ]);
+    }
+
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
-    Services.obs.addObserver(this, 'Accessibility:NextObject', false);
-    Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
-    Services.obs.addObserver(this, 'Accessibility:Focus', false);
-    Services.obs.addObserver(this, 'Accessibility:ActivateObject', false);
-    Services.obs.addObserver(this, 'Accessibility:LongPress', false);
-    Services.obs.addObserver(this, 'Accessibility:ScrollForward', false);
-    Services.obs.addObserver(this, 'Accessibility:ScrollBackward', false);
-    Services.obs.addObserver(this, 'Accessibility:MoveByGranularity', false);
     Utils.win.addEventListener('TabOpen', this);
     Utils.win.addEventListener('TabClose', this);
     Utils.win.addEventListener('TabSelect', this);
 
     if (this.readyCallback) {
       this.readyCallback();
       delete this.readyCallback;
     }
@@ -164,24 +171,29 @@ this.AccessFu = { // jshint ignore:line
     PointerAdapter.stop();
 
     Utils.win.removeEventListener('TabOpen', this);
     Utils.win.removeEventListener('TabClose', this);
     Utils.win.removeEventListener('TabSelect', this);
 
     Services.obs.removeObserver(this, 'remote-browser-shown');
     Services.obs.removeObserver(this, 'inprocess-browser-shown');
-    Services.obs.removeObserver(this, 'Accessibility:NextObject');
-    Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
-    Services.obs.removeObserver(this, 'Accessibility:Focus');
-    Services.obs.removeObserver(this, 'Accessibility:ActivateObject');
-    Services.obs.removeObserver(this, 'Accessibility:LongPress');
-    Services.obs.removeObserver(this, 'Accessibility:ScrollForward');
-    Services.obs.removeObserver(this, 'Accessibility:ScrollBackward');
-    Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
+
+    if (Utils.MozBuildApp === 'mobile/android') {
+      EventDispatcher.instance.unregisterListener(this, [
+        'Accessibility:ActivateObject',
+        'Accessibility:Focus',
+        'Accessibility:LongPress',
+        'Accessibility:MoveByGranularity',
+        'Accessibility:NextObject',
+        'Accessibility:PreviousObject',
+        'Accessibility:ScrollBackward',
+        'Accessibility:ScrollForward',
+      ]);
+    }
 
     delete this._quicknavModesPref;
     delete this._notifyOutputPref;
 
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
@@ -283,53 +295,57 @@ this.AccessFu = { // jshint ignore:line
 
   _handleMessageManager: function _handleMessageManager(aMessageManager) {
     if (this._enabled) {
       this._addMessageListeners(aMessageManager);
     }
     this._loadFrameScript(aMessageManager);
   },
 
-  observe: function observe(aSubject, aTopic, aData) {
-    switch (aTopic) {
+  onEvent: function (event, data, callback) {
+    switch (event) {
       case 'Accessibility:Settings':
-        this._systemPref = JSON.parse(aData).enabled;
+        this._systemPref = data.enabled;
         this._enableOrDisable();
         break;
       case 'Accessibility:NextObject':
-      case 'Accessibility:PreviousObject':
-      {
-        let rule = aData ?
-          aData.substr(0, 1).toUpperCase() + aData.substr(1).toLowerCase() :
+      case 'Accessibility:PreviousObject': {
+        let rule = data ?
+          data.rule.substr(0, 1).toUpperCase() + data.rule.substr(1).toLowerCase() :
           'Simple';
-        let method = aTopic.replace(/Accessibility:(\w+)Object/, 'move$1');
+        let method = event.replace(/Accessibility:(\w+)Object/, 'move$1');
         this.Input.moveCursor(method, rule, 'gesture');
         break;
       }
       case 'Accessibility:ActivateObject':
-        this.Input.activateCurrent(JSON.parse(aData));
+        this.Input.activateCurrent(data);
         break;
       case 'Accessibility:LongPress':
         this.Input.sendContextMenuMessage();
         break;
       case 'Accessibility:ScrollForward':
         this.Input.androidScroll('forward');
         break;
       case 'Accessibility:ScrollBackward':
         this.Input.androidScroll('backward');
         break;
       case 'Accessibility:Focus':
-        this._focused = JSON.parse(aData);
+        this._focused = data.gainFocus;
         if (this._focused) {
           this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
         }
         break;
       case 'Accessibility:MoveByGranularity':
-        this.Input.moveByGranularity(JSON.parse(aData));
+        this.Input.moveByGranularity(data);
         break;
+    }
+  },
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
       case 'remote-browser-shown':
       case 'inprocess-browser-shown':
       {
         // Ignore notifications that aren't from a Browser
         let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
         if (!frameLoader.ownerIsMozBrowserFrame) {
           return;
         }
--- a/accessible/tests/mochitest/jsat/output.js
+++ b/accessible/tests/mochitest/jsat/output.js
@@ -55,16 +55,17 @@ function testContextOutput(expected, aAc
 function testObjectOutput(aAccOrElmOrID, aGenerator) {
   var accessible = getAccessible(aAccOrElmOrID);
   if (!accessible.name || !accessible.name.trim()) {
     return;
   }
   var context = new PivotContext(accessible);
   var output = aGenerator.genForObject(accessible, context);
   var outputOrder;
+  // eslint-disable-next-line mozilla/use-default-preference-values
   try {
     outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
   } catch (ex) {
     // PREF_UTTERANCE_ORDER not set.
     outputOrder = 0;
   }
   var expectedNameIndex = outputOrder === 0 ? output.length - 1 : 0;
   var nameIndex = output.indexOf(accessible.name);
--- a/addon-sdk/source/test/test-weak-set.js
+++ b/addon-sdk/source/test/test-weak-set.js
@@ -76,71 +76,71 @@ exports['test add/remove/iterate/clear i
 };
 
 exports['test adding non object or null item'] = function(assert) {
   let items = {};
 
   assert.throws(() => {
     add(items, 'foo');
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, 0);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, undefined);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, null);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(items, true);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 };
 
 exports['test adding to non object or null item'] = function(assert) {
   let item = {};
 
   assert.throws(() => {
     add('foo', item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(0, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(undefined, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(null, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 
   assert.throws(() => {
     add(true, item);
   },
-  /^\w+ is not a non-null object/,
+  TypeError,
   'only non-null object are allowed');
 };
 
 require("sdk/test").run(exports);
--- a/b2g/chrome/content/devtools/adb.js
+++ b/b2g/chrome/content/devtools/adb.js
@@ -41,16 +41,17 @@ var AdbController = {
     this.updateState();
   },
 
   startDisableAdbTimer: function() {
     if (this.disableAdbTimer) {
       this.disableAdbTimer.cancel();
     } else {
       this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      // eslint-disable-next-line mozilla/use-default-preference-values
       try {
         this.disableAdbTimeoutHours =
           Services.prefs.getIntPref("b2g.adb.timeout-hours");
       } catch (e) {
         // This happens if the pref doesn't exist, in which case
         // disableAdbTimeoutHours will still be set to the default.
       }
     }
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -422,22 +422,18 @@ setUpdateTrackingId();
   let geckoPrefName = 'toolkit.telemetry.enabled';
   SettingsListener.observe(gaiaSettingName, null, function(value) {
     if (value !== null) {
       // Gaia setting has been set; update Gecko pref to that.
       Services.prefs.setBoolPref(geckoPrefName, value);
       return;
     }
     // Gaia setting has not been set; set the gaia setting to default.
-    let prefValue = AppConstants.MOZ_TELEMETRY_ON_BY_DEFAULT;
-    try {
-      prefValue = Services.prefs.getBoolPref(geckoPrefName);
-    } catch (e) {
-      // Pref not set; use default value.
-    }
+    let prefValue = Services.prefs.getBoolPref(geckoPrefName,
+                                               AppConstants.MOZ_TELEMETRY_ON_BY_DEFAULT);
     let setting = {};
     setting[gaiaSettingName] = prefValue;
     window.navigator.mozSettings.createLock().set(setting);
   });
 })();
 
 // =================== Low-precision buffer ======================
 (function setupLowPrecisionSettings() {
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -251,25 +251,21 @@ var shell = {
     SafeMode.check(window).then(() => {
       let startManifestURL =
         Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
           .getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
 
       // If --start-manifest hasn't been specified, we re-use the latest specified manifest.
       // If it's the first launch, we will fallback to b2g.default.start_manifest_url
       if (AppConstants.MOZ_GRAPHENE && !startManifestURL) {
-        try {
-          startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
-        } catch(e) {}
+        startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url", "");
       }
 
       if (!startManifestURL) {
-        try {
-          startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url");
-        } catch(e) {}
+        startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url", "");
       }
 
       if (startManifestURL) {
         Cu.import('resource://gre/modules/Bootstraper.jsm');
 
         if (AppConstants.MOZ_GRAPHENE && Bootstraper.isInstallRequired(startManifestURL)) {
           // Installing the app my take some time. We don't want to keep the
           // native window hidden.
--- a/b2g/components/AboutServiceWorkers.jsm
+++ b/b2g/components/AboutServiceWorkers.jsm
@@ -42,20 +42,17 @@ function serializeServiceWorkerInfo(aSer
 }
 
 
 this.AboutServiceWorkers = {
   get enabled() {
     if (this._enabled) {
       return this._enabled;
     }
-    this._enabled = false;
-    try {
-      this._enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
-    } catch(e) {}
+    this._enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled", false);
     return this._enabled;
   },
 
   init: function() {
     SystemAppProxy.addEventListener("mozAboutServiceWorkersContentEvent",
                                     AboutServiceWorkers);
   },
 
--- a/b2g/components/Bootstraper.jsm
+++ b/b2g/components/Bootstraper.jsm
@@ -70,16 +70,17 @@ this.Bootstraper = {
     * If a system app is already installed, uninstall it so that we can
     * cleanly replace it by the current one.
     */
   uninstallPreviousSystemApp: function() {
     // TODO: FIXME
     return Promise.resolve();
 
     let oldManifestURL;
+    // eslint-disable-next-line mozilla/use-default-preference-values
     try{
       oldManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
     } catch(e) {
       // No preference set, so nothing to uninstall.
       return Promise.resolve();
     }
 
     let id = DOMApplicationRegistry.getAppLocalIdByManifestURL(oldManifestURL);
--- a/b2g/components/DirectoryProvider.js
+++ b/b2g/components/DirectoryProvider.js
@@ -108,23 +108,17 @@ DirectoryProvider.prototype = {
   },
 
   getFileNotGonk: function(prop, persistent) {
     // In desktop builds, coreAppsDir is the same as the profile
     // directory unless otherwise specified. We just need to get the
     // path from the parent, and it is then used to build
     // jar:remoteopenfile:// uris.
     if (prop == "coreAppsDir") {
-      let coreAppsDirPref;
-      try {
-        coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF);
-      } catch (e) {
-        // coreAppsDirPref may not exist if we're on an older version
-        // of gaia, so just fail silently.
-      }
+      let coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF, "");
       let appsDir;
       // If pref doesn't exist or isn't set, default to old value
       if (!coreAppsDirPref || coreAppsDirPref == "") {
         appsDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
         appsDir.append("webapps");
       } else {
         appsDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile)
         appsDir.initWithPath(coreAppsDirPref);
--- a/b2g/components/SignInToWebsite.jsm
+++ b/b2g/components/SignInToWebsite.jsm
@@ -89,22 +89,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
 
 // The default persona uri; can be overwritten with toolkit.identity.uri pref.
 // Do this if you want to repoint to a different service for testing.
 // There's no point in setting up an observer to monitor the pref, as b2g prefs
 // can only be overwritten when the profie is recreated.  So just get the value
 // on start-up.
-var kPersonaUri = "https://firefoxos.persona.org";
-try {
-  kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri");
-} catch(noSuchPref) {
-  // stick with the default value
-}
+var kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri",
+                                             "https://firefoxos.persona.org");
 
 // JS shim that contains the callback functions that
 // live within the identity UI provisioning frame.
 const kIdentityShimFile = "chrome://b2g/content/identity.js";
 
 // Type of MozChromeEvents to handle id dialogs.
 const kOpenIdentityDialog = "id-dialog-open";
 const kDoneIdentityDialog = "id-dialog-done";
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -427,24 +427,19 @@ function show(id, childId) {
 // Migrate sync data from the default profile to the dev-edition profile.
 // Returns a promise of a true value if migration succeeded, or false if it
 // failed.
 function migrateToDevEdition(urlParams) {
   let defaultProfilePath;
   try {
     defaultProfilePath = window.getDefaultProfilePath();
   } catch (e) {} // no default profile.
-  let migrateSyncCreds = false;
-  if (defaultProfilePath) {
-    try {
-      migrateSyncCreds = Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition");
-    } catch (e) {}
-  }
 
-  if (!migrateSyncCreds) {
+  if (!defaultProfilePath ||
+      !Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition", false)) {
     return Promise.resolve(false);
   }
 
   Cu.import("resource://gre/modules/osfile.jsm");
   let fxAccountsStorage = OS.Path.join(defaultProfilePath, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
   return OS.File.read(fxAccountsStorage, { encoding: "utf-8" }).then(text => {
     let accountData = JSON.parse(text).accountData;
     updateDisplayedEmail(accountData);
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -152,20 +152,17 @@ var gFxAccounts = {
 
   handleEvent(event) {
     this._inCustomizationMode = event.type == "customizationstarting";
     this.updateUI();
   },
 
   // Note that updateUI() returns a Promise that's only used by tests.
   updateUI() {
-    let profileInfoEnabled = false;
-    try {
-      profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
-    } catch (e) { }
+    let profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled", false);
 
     this.panelUIFooter.hidden = false;
 
     // Make sure the button is disabled in customization mode.
     if (this._inCustomizationMode) {
       this.panelUIStatus.setAttribute("disabled", "true");
       this.panelUILabel.setAttribute("disabled", "true");
       this.panelUIAvatar.setAttribute("disabled", "true");
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -292,20 +292,17 @@ var gSyncUI = {
      button being in "customize purgatory" and if so, move it to the panel.
      This is done primarily for profiles created before SyncedTabs landed,
      where the button defaulted to being in that purgatory.
      We use a preference to ensure we only do it once, so people can still
      customize it away and have it stick.
   */
   maybeMoveSyncedTabsButton() {
     const prefName = "browser.migrated-sync-button";
-    let migrated = false;
-    try {
-      migrated = Services.prefs.getBoolPref(prefName);
-    } catch (_) {}
+    let migrated = Services.prefs.getBoolPref(prefName, false);
     if (migrated) {
       return;
     }
     if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
       CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
     }
     Services.prefs.setBoolPref(prefName, true);
   },
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2788,25 +2788,20 @@ var gMenuButtonBadgeManager = {
 // Setup the hamburger button badges for updates, if enabled.
 var gMenuButtonUpdateBadge = {
   enabled: false,
   badgeWaitTime: 0,
   timer: null,
   cancelObserverRegistered: false,
 
   init() {
-    try {
-      this.enabled = Services.prefs.getBoolPref("app.update.badge");
-    } catch (e) {}
+    this.enabled = Services.prefs.getBoolPref("app.update.badge", false);
     if (this.enabled) {
-      try {
-        this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime");
-      } catch (e) {
-        this.badgeWaitTime = 345600; // 4 days
-      }
+      this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime",
+                                                     345600); // 4 days
       Services.obs.addObserver(this, "update-staged", false);
       Services.obs.addObserver(this, "update-downloaded", false);
     }
   },
 
   uninit() {
     if (this.timer)
       this.timer.cancel();
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -1,17 +1,16 @@
 # to be included inside a popupset element
 
     <panel id="notification-popup"
            type="arrow"
            position="after_start"
            hidden="true"
            orient="vertical"
            noautofocus="true"
-           followanchor="false"
            role="alert"/>
 
     <popupnotification id="webRTC-shareDevices-notification" hidden="true">
       <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
         <label value="&getUserMedia.selectCamera.label;"
                accesskey="&getUserMedia.selectCamera.accesskey;"
                control="webRTC-selectCamera-menulist"/>
         <menulist id="webRTC-selectCamera-menulist">
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -729,41 +729,28 @@ Sanitizer.clearPluginData = Task.async(f
 
     // Determine age range in seconds. (-1 means clear all.) We don't know
     // that range[1] is actually now, so we compute age range based
     // on the lower bound. If range results in a negative age, do nothing.
     let age = range ? (Date.now() / 1000 - range[0] / 1000000) : -1;
     if (!range || age >= 0) {
       let tags = ph.getPluginTags();
       for (let tag of tags) {
-        let refObj = {};
-        let probe = "";
-        if (/\bFlash\b/.test(tag.name)) {
-          probe = tag.loaded ? "FX_SANITIZE_LOADED_FLASH"
-                             : "FX_SANITIZE_UNLOADED_FLASH";
-          TelemetryStopwatch.start(probe, refObj);
-        }
         try {
           let rv = yield new Promise(resolve =>
             ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, age, resolve)
           );
           // If the plugin doesn't support clearing by age, clear everything.
           if (rv == Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
             yield new Promise(resolve =>
               ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, resolve)
             );
           }
-          if (probe) {
-            TelemetryStopwatch.finish(probe, refObj);
-          }
         } catch (ex) {
           // Ignore errors from plug-ins
-          if (probe) {
-            TelemetryStopwatch.cancel(probe, refObj);
-          }
         }
       }
     }
   });
 
   try {
     // We don't want to wait for this operation to complete...
     promiseClearPluginData = promiseClearPluginData(range);
--- a/browser/base/content/sync/aboutSyncTabs.js
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -297,22 +297,17 @@ var RemoteTabViewer = {
 
       el = el.nextSibling;
     }
   },
 
   _refetchTabs(force) {
     if (!force) {
       // Don't bother refetching tabs if we already did so recently
-      let lastFetch = 0;
-      try {
-        lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch");
-      } catch (e) {
-        /* Just use the default value of 0 */
-      }
+      let lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch", 0);
 
       let now = Math.floor(Date.now() / 1000);
       if (now - lastFetch < 30) {
         return false;
       }
     }
 
     // Ask Sync to just do the tabs engine if it can.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4465,21 +4465,17 @@
             _useDumpForLogging: false,
             _logInit: false,
 
             logging() {
               if (this._useDumpForLogging)
                 return true;
               if (this._logInit)
                 return this._shouldLog;
-              let result = false;
-              try {
-                result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
-              } catch (ex) {
-              }
+              let result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming", false);
               this._shouldLog = result;
               this._logInit = true;
               return this._shouldLog;
             },
 
             tinfo(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
@@ -5642,21 +5638,17 @@
 
           var tab = this.firstChild;
           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
 
           window.addEventListener("resize", this);
           window.addEventListener("load", this);
 
-          try {
-            this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
-          } catch (ex) {
-            this._tabAnimationLoggingEnabled = false;
-          }
+          this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled", false);
           this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
           Services.prefs.addObserver("privacy.userContext", this, false);
           this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -111,16 +111,25 @@ const TRANSPARENT_SELECT =
   "<html><head><style>" +
   "  #one { background-color: transparent; }" +
   "</style>" +
   "<body><select id='one'>" +
   '  <option value="One">{"unstyled": "true"}</option>' +
   '  <option value="Two" selected="true">{"end": "true"}</option>' +
   "</select></body></html>";
 
+const OPTION_COLOR_EQUAL_TO_UABACKGROUND_COLOR_SELECT =
+  "<html><head><style>" +
+  "  #one { background-color: black; color: white; }" +
+  "</style>" +
+  "<body><select id='one'>" +
+  '  <option value="One" style="background-color: white; color: black;">{"color": "rgb(0, 0, 0)", "backgroundColor": "rgb(255, 255, 255)"}</option>' +
+  '  <option value="Two" selected="true">{"end": "true"}</option>' +
+  "</select></body></html>";
+
 function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
 
   if (mode == "click" || mode == "mousedown") {
     let mousePromise;
     if (mode == "click") {
       mousePromise = BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser);
     } else {
@@ -925,8 +934,46 @@ add_task(function* test_blur_hides_popup
   });
 
   yield popupHiddenPromise;
 
   ok(true, "Blur closed popup");
 
   yield BrowserTestUtils.removeTab(tab);
 });
+
+// This test checks when a <select> element has a background set, and the
+// options have their own background set which is equal to the default
+// user-agent background color, but should be used because the select
+// background color has been changed.
+add_task(function* test_options_inverted_from_select_background() {
+  const pageUrl = "data:text/html," + escape(OPTION_COLOR_EQUAL_TO_UABACKGROUND_COLOR_SELECT);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+
+  is(selectPopup.parentNode.itemCount, 2, "Correct number of items");
+  let child = selectPopup.firstChild;
+  let idx = 1;
+
+  // The popup has a black background and white text, but the
+  // options inside of it have flipped the colors.
+  is(getComputedStyle(selectPopup).color, "rgb(255, 255, 255)",
+    "popup has expected foreground color");
+  is(getComputedStyle(selectPopup).backgroundColor, "rgb(0, 0, 0)",
+    "popup has expected background color");
+
+  ok(!child.selected, "The first child should not be selected");
+  while (child) {
+    testOptionColors(idx, child, menulist);
+    idx++;
+    child = child.nextSibling;
+  }
+
+  yield hideSelectPopup(selectPopup, "escape");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/base/content/test/general/offlineByDefault.js
+++ b/browser/base/content/test/general/offlineByDefault.js
@@ -1,17 +1,13 @@
 var offlineByDefault = {
   defaultValue: false,
   prefBranch: SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch),
   set(allow) {
-    try {
-      this.defaultValue = this.prefBranch.getBoolPref("offline-apps.allow_by_default");
-    } catch (e) {
-      this.defaultValue = false
-    }
+    this.defaultValue = this.prefBranch.getBoolPref("offline-apps.allow_by_default", false);
     this.prefBranch.setBoolPref("offline-apps.allow_by_default", allow);
   },
   reset() {
     this.prefBranch.setBoolPref("offline-apps.allow_by_default", this.defaultValue);
   }
 }
 
 offlineByDefault.set(false);
--- a/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
@@ -1,17 +1,17 @@
 // Tests referrer on context menu navigation - open link in new private window.
 // Selects "open link in new private window" from the context menu.
 
 function startNewPrivateWindowTestCase(aTestNumber) {
   info("browser_referrer_open_link_in_private: " +
        getReferrerTestDescription(aTestNumber));
   contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
     newWindowOpened().then(function(aNewWindow) {
-      someTabLoaded(aNewWindow).then(function() {
+      BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
         checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
                                       startNewPrivateWindowTestCase);
       });
     });
 
     doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkprivate");
   });
 }
--- a/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
@@ -1,17 +1,17 @@
 // Tests referrer on context menu navigation - open link in new window.
 // Selects "open link in new window" from the context menu.
 
 function startNewWindowTestCase(aTestNumber) {
   info("browser_referrer_open_link_in_window: " +
        getReferrerTestDescription(aTestNumber));
   contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
     newWindowOpened().then(function(aNewWindow) {
-      someTabLoaded(aNewWindow).then(function() {
+      BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
         checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
                                       startNewWindowTestCase);
       });
     });
 
     doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
   });
 }
--- a/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js
@@ -4,17 +4,17 @@
 // This test runs from a container tab. The new tab/window will be loaded in
 // the same container.
 
 function startNewWindowTestCase(aTestNumber) {
   info("browser_referrer_open_link_in_window: " +
        getReferrerTestDescription(aTestNumber));
   contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
     newWindowOpened().then(function(aNewWindow) {
-      someTabLoaded(aNewWindow).then(function() {
+      BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
         checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
                                       startNewWindowTestCase,
                                       { userContextId: 1 });
       });
     });
 
     doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
   });
--- a/browser/base/content/test/referrer/browser_referrer_simple_click.js
+++ b/browser/base/content/test/referrer/browser_referrer_simple_click.js
@@ -1,18 +1,20 @@
 // Tests referrer on simple click navigation.
 // Clicks on the link, which opens it in the same tab.
 
 function startSimpleClickTestCase(aTestNumber) {
   info("browser_referrer_simple_click: " +
        getReferrerTestDescription(aTestNumber));
-  BrowserTestUtils.browserLoaded(gTestWindow.gBrowser.selectedBrowser).then(function() {
-    checkReferrerAndStartNextTest(aTestNumber, null, null,
-                                  startSimpleClickTestCase);
-  });
+  BrowserTestUtils.browserLoaded(gTestWindow.gBrowser.selectedBrowser, false,
+                                 (url) => url.endsWith("file_referrer_testserver.sjs"))
+                  .then(function() {
+                    checkReferrerAndStartNextTest(aTestNumber, null, null,
+                                                  startSimpleClickTestCase);
+                  });
 
   clickTheLink(gTestWindow, "testlink", {});
 }
 
 function test() {
   requestLongerTimeout(10);  // slowwww shutdown on e10s
   startReferrerTest(startSimpleClickTestCase);
 }
--- a/browser/base/content/test/referrer/head.js
+++ b/browser/base/content/test/referrer/head.js
@@ -135,28 +135,18 @@ function delayedStartupFinished(aWindow)
 
 /**
  * Waits for some (any) tab to load. The caller triggers the load.
  * @param aWindow The window where to wait for a tab to load.
  * @return {Promise}
  * @resolves With the tab once it's loaded.
  */
 function someTabLoaded(aWindow) {
-  return new Promise(function(resolve) {
-    aWindow.gBrowser.addEventListener("load", function onLoad(aEvent) {
-      if (aWindow.location.href === "about:blank") {
-        return;
-      }
-      let tab = aWindow.gBrowser._getTabForContentWindow(
-          aEvent.target.defaultView.top);
-      if (tab) {
-        aWindow.gBrowser.removeEventListener("load", onLoad, true);
-        resolve(tab);
-      }
-    }, true);
+  return BrowserTestUtils.waitForNewTab(gTestWindow.gBrowser).then((tab) => {
+    return BrowserTestUtils.browserStopped(tab.linkedBrowser).then(() => tab);
   });
 }
 
 /**
  * Waits for a new window to open and load. The caller triggers the open.
  * @return {Promise}
  * @resolves With the new window once it's open and loaded.
  */
@@ -204,17 +194,17 @@ function referrerTestCaseLoaded(aTestNum
                              REFERRER_POLICYSERVER_URL_ATTRIBUTE;
   let url = test.fromScheme + server +
             "?scheme=" + escape(test.toScheme) +
             "&policy=" + escape(test.policy || "") +
             "&rel=" + escape(test.rel || "");
   let browser = gTestWindow.gBrowser;
   return BrowserTestUtils.openNewForegroundTab(browser, () => {
     browser.selectedTab = browser.addTab(url, aParams);
-  });
+  }, false, true);
 }
 
 /**
  * Checks the result of the referrer test, and moves on to the next test.
  * @param aTestNumber The test number - 0, 1, 2, ...
  * @param aNewWindow The new window where the referrer target opened, or null.
  * @param aNewTab The new tab where the referrer target opened, or null.
  * @param aStartTestCase The callback to start the next test, aTestNumber + 1.
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -153,20 +153,17 @@ var gUIStateBeforeReset = {
   uiCustomizationState: null,
   drawInTitlebar: null,
   currentTheme: null,
 };
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
-  let debug;
-  try {
-    debug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
-  } catch (ex) {}
+  let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: debug ? "all" : "log",
     prefix: "CustomizableUI",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 var CustomizableUIInternal = {
@@ -1921,26 +1918,20 @@ var CustomizableUIInternal = {
   // Note that this does not populate gPlacements, which is done lazily so that
   // the legacy state can be migrated, which is only available once a browser
   // window is openned.
   // The panel area is an exception here, since it has no legacy state and is
   // built lazily - and therefore wouldn't otherwise result in restoring its
   // state immediately when a browser window opens, which is important for
   // other consumers of this API.
   loadSavedState() {
-    let state = null;
-    try {
-      state = Services.prefs.getCharPref(kPrefCustomizationState);
-    } catch (e) {
+    let state = Services.prefs.getCharPref(kPrefCustomizationState, "");
+    if (!state) {
       log.debug("No saved state found");
-      // This will fail if nothing has been customized, so silently fall back to
-      // the defaults.
-    }
-
-    if (!state) {
+      // Nothing has been customized, so silently fall back to the defaults.
       return;
     }
     try {
       gSavedState = JSON.parse(state);
       if (typeof gSavedState != "object" || gSavedState === null) {
         throw "Invalid saved state";
       }
     } catch (e) {
@@ -2215,20 +2206,17 @@ var CustomizableUIInternal = {
     // onWidgetAdded event - our own handler will take care of adding it to
     // any build areas.
     this.beginBatchUpdate();
     try {
       if (widget.currentArea) {
         this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
                              widget.currentPosition);
       } else if (widgetMightNeedAutoAdding) {
-        let autoAdd = true;
-        try {
-          autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
-        } catch (e) {}
+        let autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd, true);
 
         // If the widget doesn't have an existing placement, and it hasn't been
         // seen before, then add it to its default area so it can be used.
         // If the widget is not removable, we *have* to add it to its default
         // area here.
         let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
         if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
           if (widget.defaultArea) {
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -41,20 +41,17 @@ XPCOMUtils.defineLazyGetter(this, "Brand
 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const kPrefCustomizationDebug = "browser.uiCustomization.debug";
 const kWidePanelItemClass = "panel-wide-item";
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
-  let debug;
-  try {
-    debug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
-  } catch (ex) {}
+  let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: debug ? "all" : "log",
     prefix: "CustomizableWidgets",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -37,19 +37,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
 let gDebug;
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
-  try {
-    gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
-  } catch (ex) {}
+  gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: gDebug ? "all" : "log",
     prefix: "CustomizeMode",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 var gDisableAnimation = null;
@@ -619,21 +617,18 @@ CustomizeMode.prototype = {
     let deck = this.document.getElementById("tab-view-deck");
     let toolboxRect = this.window.gNavToolbox.getBoundingClientRect();
     let height = toolboxRect.bottom;
     deck.style.setProperty("--toolbox-rect-height", `${height}`);
     deck.style.setProperty("--toolbox-rect-height-with-unit", `${height}px`);
   },
 
   maybeShowTip(aAnchor) {
-    let shown = false;
     const kShownPref = "browser.customizemode.tip0.shown";
-    try {
-      shown = Services.prefs.getBoolPref(kShownPref);
-    } catch (ex) {}
+    let shown = Services.prefs.getBoolPref(kShownPref, false);
     if (shown)
       return;
 
     let anchorNode = aAnchor || this.document.getElementById("customization-panelHolder");
     let messageNode = this.tipPanel.querySelector(".customization-tipPanel-contentMessage");
     if (!messageNode.childElementCount) {
       // Put the tip contents in the popup.
       let bundle = this.document.getElementById("bundle_browser");
@@ -1497,20 +1492,17 @@ CustomizeMode.prototype = {
         break;
     }
   },
 
   _updateTitlebarButton() {
     if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
       return;
     }
-    let drawInTitlebar = true;
-    try {
-      drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref);
-    } catch (ex) { }
+    let drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref, true);
     let button = this.document.getElementById("customization-titlebar-visibility-button");
     // Drawing in the titlebar means 'hiding' the titlebar:
     if (drawInTitlebar) {
       button.removeAttribute("checked");
     } else {
       button.setAttribute("checked", "true");
     }
   },
--- a/browser/components/customizableui/test/browser_1042100_default_placements_update.js
+++ b/browser/components/customizableui/test/browser_1042100_default_placements_update.js
@@ -76,20 +76,17 @@ function test() {
     // versioned facility.  They're independent of kVersion and the saved
     // state's current version, so they may be present in the placements.
     for (let i = 0; i < placements.length; ) {
       if (placements[i] == testWidgetNew.id) {
         i++;
         continue;
       }
       let pref = "browser.toolbarbuttons.introduced." + placements[i];
-      let introduced = false;
-      try {
-        introduced = Services.prefs.getBoolPref(pref);
-      } catch (ex) {}
+      let introduced = Services.prefs.getBoolPref(pref, false);
       if (!introduced) {
         i++;
         continue;
       }
       placements.splice(i, 1);
     }
 
     is(placements.length, 1, "Should have 1 newly placed widget in nav-bar");
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -17,20 +17,17 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 
 this.DistributionCustomizer = function DistributionCustomizer() {
   // For parallel xpcshell testing purposes allow loading the distribution.ini
   // file from the profile folder through an hidden pref.
-  let loadFromProfile = false;
-  try {
-    loadFromProfile = Services.prefs.getBoolPref("distribution.testing.loadFromProfile");
-  } catch (ex) {}
+  let loadFromProfile = Services.prefs.getBoolPref("distribution.testing.loadFromProfile", false);
   let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
                getService(Ci.nsIProperties);
   try {
     let iniFile = loadFromProfile ? dirSvc.get("ProfD", Ci.nsIFile)
                                   : dirSvc.get("XREAppDist", Ci.nsIFile);
     if (loadFromProfile) {
       iniFile.leafName = "distribution";
     }
@@ -55,22 +52,17 @@ DistributionCustomizer.prototype = {
       // Unable to parse INI.
       Cu.reportError("Unable to parse distribution.ini");
     }
     this.__defineGetter__("_ini", () => ini);
     return this._ini;
   },
 
   get _locale() {
-    let locale;
-    try {
-      locale = this._prefs.getCharPref("general.useragent.locale");
-    } catch (e) {
-      locale = "en-US";
-    }
+    let locale = this._prefs.getCharPref("general.useragent.locale", "en-US");
     this.__defineGetter__("_locale", () => locale);
     return this._locale;
   },
 
   get _language() {
     let language = this._locale.split("-")[0];
     this.__defineGetter__("_language", () => language);
     return this._language;
@@ -284,20 +276,17 @@ DistributionCustomizer.prototype = {
     try {
       bmProcessedPref = this._ini.getString("Global",
                                             "bookmarks.initialized.pref");
     } catch (e) {
       bmProcessedPref = "distribution." +
         this._ini.getString("Global", "id") + ".bookmarksProcessed";
     }
 
-    let bmProcessed = false;
-    try {
-      bmProcessed = this._prefs.getBoolPref(bmProcessedPref);
-    } catch (e) {}
+    let bmProcessed = this._prefs.getBoolPref(bmProcessedPref, false);
 
     if (!bmProcessed) {
       if (sections["BookmarksMenu"])
         yield this._parseBookmarksSection(PlacesUtils.bookmarks.menuGuid,
                                           "BookmarksMenu");
       if (sections["BookmarksToolbar"])
         yield this._parseBookmarksSection(PlacesUtils.bookmarks.toolbarGuid,
                                           "BookmarksToolbar");
--- a/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
@@ -1,601 +1,658 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-function backgroundScript() {
-  let unsortedId, ourId;
-  let initialBookmarkCount = 0;
-  let createdBookmarks = new Set();
-  let createdFolderId;
-  let collectedEvents = [];
-  const nonExistentId = "000000000000";
-  const bookmarkGuids = {
-    menuGuid:    "menu________",
-    toolbarGuid: "toolbar_____",
-    unfiledGuid: "unfiled_____",
-  };
-
-  function checkOurBookmark(bookmark) {
-    browser.test.assertEq(ourId, bookmark.id, "Bookmark has the expected Id");
-    browser.test.assertTrue("parentId" in bookmark, "Bookmark has a parentId");
-    browser.test.assertEq(0, bookmark.index, "Bookmark has the expected index"); // We assume there are no other bookmarks.
-    browser.test.assertEq("http://example.org/", bookmark.url, "Bookmark has the expected url");
-    browser.test.assertEq("test bookmark", bookmark.title, "Bookmark has the expected title");
-    browser.test.assertTrue("dateAdded" in bookmark, "Bookmark has a dateAdded");
-    browser.test.assertFalse("dateGroupModified" in bookmark, "Bookmark does not have a dateGroupModified");
-    browser.test.assertFalse("unmodifiable" in bookmark, "Bookmark is not unmodifiable");
-  }
-
-  function checkBookmark(expected, bookmark) {
-    browser.test.assertEq(expected.url, bookmark.url, "Bookmark has the expected url");
-    browser.test.assertEq(expected.title, bookmark.title, "Bookmark has the expected title");
-    browser.test.assertEq(expected.index, bookmark.index, "Bookmark has expected index");
-    if ("parentId" in expected) {
-      browser.test.assertEq(expected.parentId, bookmark.parentId, "Bookmark has the expected parentId");
-    }
-  }
-
-  function expectedError() {
-    browser.test.fail("Did not get expected error");
-  }
-
-  function checkOnCreated(id, parentId, index, title, url, dateAdded) {
-    let createdData = collectedEvents.pop();
-    browser.test.assertEq("onCreated", createdData.event, "onCreated was the last event received");
-    browser.test.assertEq(id, createdData.id, "onCreated event received the expected id");
-    let bookmark = createdData.bookmark;
-    browser.test.assertEq(id, bookmark.id, "onCreated event received the expected bookmark id");
-    browser.test.assertEq(parentId, bookmark.parentId, "onCreated event received the expected bookmark parentId");
-    browser.test.assertEq(index, bookmark.index, "onCreated event received the expected bookmark index");
-    browser.test.assertEq(title, bookmark.title, "onCreated event received the expected bookmark title");
-    browser.test.assertEq(url, bookmark.url, "onCreated event received the expected bookmark url");
-    browser.test.assertEq(dateAdded, bookmark.dateAdded, "onCreated event received the expected bookmark dateAdded");
-  }
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
 
-  function checkOnChanged(id, url, title) {
-    // If both url and title are changed, then url is fired last.
-    let changedData = collectedEvents.pop();
-    browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
-    browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
-    browser.test.assertEq(url, changedData.info.url, "onChanged event received the expected url");
-    // title is fired first.
-    changedData = collectedEvents.pop();
-    browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
-    browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
-    browser.test.assertEq(title, changedData.info.title, "onChanged event received the expected title");
-  }
-
-  function checkOnMoved(id, parentId, oldParentId, index, oldIndex) {
-    let movedData = collectedEvents.pop();
-    browser.test.assertEq("onMoved", movedData.event, "onMoved was the last event received");
-    browser.test.assertEq(id, movedData.id, "onMoved event received the expected id");
-    let info = movedData.info;
-    browser.test.assertEq(parentId, info.parentId, "onMoved event received the expected parentId");
-    browser.test.assertEq(oldParentId, info.oldParentId, "onMoved event received the expected oldParentId");
-    browser.test.assertEq(index, info.index, "onMoved event received the expected index");
-    browser.test.assertEq(oldIndex, info.oldIndex, "onMoved event received the expected oldIndex");
-  }
-
-  function checkOnRemoved(id, parentId, index, url) {
-    let removedData = collectedEvents.pop();
-    browser.test.assertEq("onRemoved", removedData.event, "onRemoved was the last event received");
-    browser.test.assertEq(id, removedData.id, "onRemoved event received the expected id");
-    let info = removedData.info;
-    browser.test.assertEq(parentId, removedData.info.parentId, "onRemoved event received the expected parentId");
-    browser.test.assertEq(index, removedData.info.index, "onRemoved event received the expected index");
-    let node = info.node;
-    browser.test.assertEq(id, node.id, "onRemoved event received the expected node id");
-    browser.test.assertEq(parentId, node.parentId, "onRemoved event received the expected node parentId");
-    browser.test.assertEq(index, node.index, "onRemoved event received the expected node index");
-    browser.test.assertEq(url, node.url, "onRemoved event received the expected node url");
-  }
-
-  browser.bookmarks.onChanged.addListener((id, info) => {
-    collectedEvents.push({event: "onChanged", id, info});
-  });
-
-  browser.bookmarks.onCreated.addListener((id, bookmark) => {
-    collectedEvents.push({event: "onCreated", id, bookmark});
-  });
-
-  browser.bookmarks.onMoved.addListener((id, info) => {
-    collectedEvents.push({event: "onMoved", id, info});
-  });
-
-  browser.bookmarks.onRemoved.addListener((id, info) => {
-    collectedEvents.push({event: "onRemoved", id, info});
-  });
-
-  browser.bookmarks.get(["not-a-bookmark-guid"]).then(expectedError, invalidGuidError => {
-    browser.test.assertTrue(
-      invalidGuidError.message.includes("Invalid value for property 'guid': not-a-bookmark-guid"),
-      "Expected error thrown when trying to get a bookmark using an invalid guid"
-    );
+add_task(async function test_bookmarks() {
+  function background() {
+    let unsortedId, ourId;
+    let initialBookmarkCount = 0;
+    let createdBookmarks = new Set();
+    let createdFolderId;
+    let collectedEvents = [];
+    const nonExistentId = "000000000000";
+    const bookmarkGuids = {
+      menuGuid:    "menu________",
+      toolbarGuid: "toolbar_____",
+      unfiledGuid: "unfiled_____",
+    };
 
-    return browser.bookmarks.get([nonExistentId]).then(expectedError, nonExistentIdError => {
-      browser.test.assertTrue(
-        nonExistentIdError.message.includes("Bookmark not found"),
-        "Expected error thrown when trying to get a bookmark using a non-existent Id"
-      );
-    });
-  }).then(() => {
-    return browser.bookmarks.search({});
-  }).then(results => {
-    initialBookmarkCount = results.length;
-    return browser.bookmarks.create({title: "test bookmark", url: "http://example.org"});
-  }).then(result => {
-    ourId = result.id;
-    checkOurBookmark(result);
-    browser.test.assertEq(1, collectedEvents.length, "1 expected event received");
-    checkOnCreated(ourId, bookmarkGuids.unfiledGuid, 0, "test bookmark", "http://example.org/", result.dateAdded);
-
-    return browser.bookmarks.get(ourId);
-  }).then(results => {
-    browser.test.assertEq(results.length, 1);
-    checkOurBookmark(results[0]);
-
-    unsortedId = results[0].parentId;
-    return browser.bookmarks.get(unsortedId);
-  }).then(results => {
-    let folder = results[0];
-    browser.test.assertEq(1, results.length, "1 bookmark was returned");
-
-    browser.test.assertEq(unsortedId, folder.id, "Folder has the expected id");
-    browser.test.assertTrue("parentId" in folder, "Folder has a parentId");
-    browser.test.assertTrue("index" in folder, "Folder has an index");
-    browser.test.assertFalse("url" in folder, "Folder does not have a url");
-    browser.test.assertEq("Other Bookmarks", folder.title, "Folder has the expected title");
-    browser.test.assertTrue("dateAdded" in folder, "Folder has a dateAdded");
-    browser.test.assertTrue("dateGroupModified" in folder, "Folder has a dateGroupModified");
-    browser.test.assertFalse("unmodifiable" in folder, "Folder is not unmodifiable"); // TODO: Do we want to enable this?
-
-    return browser.bookmarks.getChildren(unsortedId);
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "The folder has one child");
-    checkOurBookmark(results[0]);
-
-    return browser.bookmarks.update(nonExistentId, {title: "new test title"}).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("No bookmarks found for the provided GUID"),
-        "Expected error thrown when trying to update a non-existent bookmark"
-      );
-
-      return browser.bookmarks.update(ourId, {title: "new test title", url: "http://example.com/"});
-    });
-  }).then(result => {
-    browser.test.assertEq("new test title", result.title, "Updated bookmark has the expected title");
-    browser.test.assertEq("http://example.com/", result.url, "Updated bookmark has the expected URL");
-    browser.test.assertEq(ourId, result.id, "Updated bookmark has the expected id");
-
-    browser.test.assertEq(2, collectedEvents.length, "2 expected events received");
-    checkOnChanged(ourId, "http://example.com/", "new test title");
+    function checkOurBookmark(bookmark) {
+      browser.test.assertEq(ourId, bookmark.id, "Bookmark has the expected Id");
+      browser.test.assertTrue("parentId" in bookmark, "Bookmark has a parentId");
+      browser.test.assertEq(0, bookmark.index, "Bookmark has the expected index"); // We assume there are no other bookmarks.
+      browser.test.assertEq("http://example.org/", bookmark.url, "Bookmark has the expected url");
+      browser.test.assertEq("test bookmark", bookmark.title, "Bookmark has the expected title");
+      browser.test.assertTrue("dateAdded" in bookmark, "Bookmark has a dateAdded");
+      browser.test.assertFalse("dateGroupModified" in bookmark, "Bookmark does not have a dateGroupModified");
+      browser.test.assertFalse("unmodifiable" in bookmark, "Bookmark is not unmodifiable");
+    }
 
-    return Promise.resolve().then(() => {
-      return browser.bookmarks.update(ourId, {url: "this is not a valid url"});
-    }).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Invalid bookmark:"),
-        "Expected error thrown when trying update with an invalid url"
-      );
-      return browser.bookmarks.getTree();
-    });
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "getTree returns one result");
-    let bookmark = results[0].children.find(bookmarkItem => bookmarkItem.id == unsortedId);
-    browser.test.assertEq(
-        "Other Bookmarks",
-        bookmark.title,
-        "Folder returned from getTree has the expected title"
-    );
-
-    return browser.bookmarks.create({parentId: "invalid"}).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Invalid bookmark"),
-        "Expected error thrown when trying to create a bookmark with an invalid parentId"
-      );
-      browser.test.assertTrue(
-          error.message.includes(`"parentGuid":"invalid"`),
-          "Expected error thrown when trying to create a bookmark with an invalid parentId"
-      );
-    });
-  }).then(() => {
-    return browser.bookmarks.remove(ourId);
-  }).then(result => {
-    browser.test.assertEq(undefined, result, "Removing a bookmark returns undefined");
-
-    browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-    checkOnRemoved(ourId, bookmarkGuids.unfiledGuid, 0, "http://example.com/");
-
-    return browser.bookmarks.get(ourId).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Bookmark not found"),
-        "Expected error thrown when trying to get a removed bookmark"
-      );
-    });
-  }).then(() => {
-    return browser.bookmarks.remove(nonExistentId).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("No bookmarks found for the provided GUID"),
-        "Expected error thrown when trying removed a non-existent bookmark"
-      );
-    });
-  }).then(() => {
-    // test bookmarks.search
-    return Promise.all([
-      browser.bookmarks.create({title: "MØzillä", url: "http://møzîllä.örg/"}),
-      browser.bookmarks.create({title: "Example", url: "http://example.org/"}),
-      browser.bookmarks.create({title: "Mozilla Folder"}),
-      browser.bookmarks.create({title: "EFF", url: "http://eff.org/"}),
-      browser.bookmarks.create({title: "Menu Item", url: "http://menu.org/", parentId: bookmarkGuids.menuGuid}),
-      browser.bookmarks.create({title: "Toolbar Item", url: "http://toolbar.org/", parentId: bookmarkGuids.toolbarGuid}),
-    ]);
-  }).then(results => {
-    browser.test.assertEq(6, collectedEvents.length, "6 expected events received");
-    checkOnCreated(results[5].id, bookmarkGuids.toolbarGuid, 0, "Toolbar Item", "http://toolbar.org/", results[5].dateAdded);
-    checkOnCreated(results[4].id, bookmarkGuids.menuGuid, 0, "Menu Item", "http://menu.org/", results[4].dateAdded);
-    checkOnCreated(results[3].id, bookmarkGuids.unfiledGuid, 0, "EFF", "http://eff.org/", results[3].dateAdded);
-    checkOnCreated(results[2].id, bookmarkGuids.unfiledGuid, 0, "Mozilla Folder", undefined, results[2].dateAdded);
-    checkOnCreated(results[1].id, bookmarkGuids.unfiledGuid, 0, "Example", "http://example.org/", results[1].dateAdded);
-    checkOnCreated(results[0].id, bookmarkGuids.unfiledGuid, 0, "MØzillä", "http://møzîllä.örg/", results[0].dateAdded);
-
-    for (let result of results) {
-      if (result.title !== "Mozilla Folder") {
-        createdBookmarks.add(result.id);
+    function checkBookmark(expected, bookmark) {
+      browser.test.assertEq(expected.url, bookmark.url, "Bookmark has the expected url");
+      browser.test.assertEq(expected.title, bookmark.title, "Bookmark has the expected title");
+      browser.test.assertEq(expected.index, bookmark.index, "Bookmark has expected index");
+      if ("parentId" in expected) {
+        browser.test.assertEq(expected.parentId, bookmark.parentId, "Bookmark has the expected parentId");
       }
     }
-    let folderResult = results[2];
-    createdFolderId = folderResult.id;
-    return Promise.all([
-      browser.bookmarks.create({title: "Mozilla", url: "http://allizom.org/", parentId: createdFolderId}),
-      browser.bookmarks.create({title: "Mozilla Corporation", url: "http://allizom.com/", parentId: createdFolderId}),
-      browser.bookmarks.create({title: "Firefox", url: "http://allizom.org/firefox/", parentId: createdFolderId}),
-    ]).then(newBookmarks => {
-      browser.test.assertEq(3, collectedEvents.length, "3 expected events received");
-      checkOnCreated(newBookmarks[2].id, createdFolderId, 0, "Firefox", "http://allizom.org/firefox/", newBookmarks[2].dateAdded);
-      checkOnCreated(newBookmarks[1].id, createdFolderId, 0, "Mozilla Corporation", "http://allizom.com/", newBookmarks[1].dateAdded);
-      checkOnCreated(newBookmarks[0].id, createdFolderId, 0, "Mozilla", "http://allizom.org/", newBookmarks[0].dateAdded);
+
+    function expectedError() {
+      browser.test.fail("Did not get expected error");
+    }
 
-      return browser.bookmarks.create({
-        title: "About Mozilla",
-        url: "http://allizom.org/about/",
-        parentId: createdFolderId,
-        index: 1,
-      });
-    }).then(result => {
-      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnCreated(result.id, createdFolderId, 1, "About Mozilla", "http://allizom.org/about/", result.dateAdded);
+    function checkOnCreated(id, parentId, index, title, url, dateAdded) {
+      let createdData = collectedEvents.pop();
+      browser.test.assertEq("onCreated", createdData.event, "onCreated was the last event received");
+      browser.test.assertEq(id, createdData.id, "onCreated event received the expected id");
+      let bookmark = createdData.bookmark;
+      browser.test.assertEq(id, bookmark.id, "onCreated event received the expected bookmark id");
+      browser.test.assertEq(parentId, bookmark.parentId, "onCreated event received the expected bookmark parentId");
+      browser.test.assertEq(index, bookmark.index, "onCreated event received the expected bookmark index");
+      browser.test.assertEq(title, bookmark.title, "onCreated event received the expected bookmark title");
+      browser.test.assertEq(url, bookmark.url, "onCreated event received the expected bookmark url");
+      browser.test.assertEq(dateAdded, bookmark.dateAdded, "onCreated event received the expected bookmark dateAdded");
+    }
 
-      // returns all items on empty object
-      return browser.bookmarks.search({});
-    }).then(bookmarksSearchResults => {
-      browser.test.assertTrue(bookmarksSearchResults.length >= 9, "At least as many bookmarks as added were returned by search({})");
+    function checkOnChanged(id, url, title) {
+      // If both url and title are changed, then url is fired last.
+      let changedData = collectedEvents.pop();
+      browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
+      browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
+      browser.test.assertEq(url, changedData.info.url, "onChanged event received the expected url");
+      // title is fired first.
+      changedData = collectedEvents.pop();
+      browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
+      browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
+      browser.test.assertEq(title, changedData.info.title, "onChanged event received the expected title");
+    }
 
-      return Promise.resolve().then(() => {
-        return browser.bookmarks.remove(createdFolderId);
-      }).then(expectedError, error => {
-        browser.test.assertTrue(
-          error.message.includes("Cannot remove a non-empty folder"),
-          "Expected error thrown when trying to remove a non-empty folder"
-        );
-        return browser.bookmarks.getSubTree(createdFolderId);
-      });
+    function checkOnMoved(id, parentId, oldParentId, index, oldIndex) {
+      let movedData = collectedEvents.pop();
+      browser.test.assertEq("onMoved", movedData.event, "onMoved was the last event received");
+      browser.test.assertEq(id, movedData.id, "onMoved event received the expected id");
+      let info = movedData.info;
+      browser.test.assertEq(parentId, info.parentId, "onMoved event received the expected parentId");
+      browser.test.assertEq(oldParentId, info.oldParentId, "onMoved event received the expected oldParentId");
+      browser.test.assertEq(index, info.index, "onMoved event received the expected index");
+      browser.test.assertEq(oldIndex, info.oldIndex, "onMoved event received the expected oldIndex");
+    }
+
+    function checkOnRemoved(id, parentId, index, url) {
+      let removedData = collectedEvents.pop();
+      browser.test.assertEq("onRemoved", removedData.event, "onRemoved was the last event received");
+      browser.test.assertEq(id, removedData.id, "onRemoved event received the expected id");
+      let info = removedData.info;
+      browser.test.assertEq(parentId, removedData.info.parentId, "onRemoved event received the expected parentId");
+      browser.test.assertEq(index, removedData.info.index, "onRemoved event received the expected index");
+      let node = info.node;
+      browser.test.assertEq(id, node.id, "onRemoved event received the expected node id");
+      browser.test.assertEq(parentId, node.parentId, "onRemoved event received the expected node parentId");
+      browser.test.assertEq(index, node.index, "onRemoved event received the expected node index");
+      browser.test.assertEq(url, node.url, "onRemoved event received the expected node url");
+    }
+
+    browser.bookmarks.onChanged.addListener((id, info) => {
+      collectedEvents.push({event: "onChanged", id, info});
     });
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of nodes returned by getSubTree");
-    browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
-    let children = results[0].children;
-    browser.test.assertEq(4, children.length, "Expected number of bookmarks returned by getSubTree");
-    browser.test.assertEq("Firefox", children[0].title, "Bookmark has the expected title");
-    browser.test.assertEq("About Mozilla", children[1].title, "Bookmark has the expected title");
-    browser.test.assertEq(1, children[1].index, "Bookmark has the expected index");
-    browser.test.assertEq("Mozilla Corporation", children[2].title, "Bookmark has the expected title");
-    browser.test.assertEq("Mozilla", children[3].title, "Bookmark has the expected title");
+
+    browser.bookmarks.onCreated.addListener((id, bookmark) => {
+      collectedEvents.push({event: "onCreated", id, bookmark});
+    });
 
-    // throws an error for invalid query objects
-    Promise.resolve().then(() => {
-      return browser.bookmarks.search();
-    }).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Incorrect argument types for bookmarks.search"),
-        "Expected error thrown when trying to search with no arguments"
-      );
+    browser.bookmarks.onMoved.addListener((id, info) => {
+      collectedEvents.push({event: "onMoved", id, info});
+    });
+
+    browser.bookmarks.onRemoved.addListener((id, info) => {
+      collectedEvents.push({event: "onRemoved", id, info});
     });
 
-    Promise.resolve().then(() => {
-      return browser.bookmarks.search(null);
-    }).then(expectedError, error => {
+    browser.bookmarks.get(["not-a-bookmark-guid"]).then(expectedError, invalidGuidError => {
       browser.test.assertTrue(
-        error.message.includes("Incorrect argument types for bookmarks.search"),
-        "Expected error thrown when trying to search with null as an argument"
+        invalidGuidError.message.includes("Invalid value for property 'guid': not-a-bookmark-guid"),
+        "Expected error thrown when trying to get a bookmark using an invalid guid"
       );
-    });
-
-    Promise.resolve().then(() => {
-      return browser.bookmarks.search(function() {});
-    }).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Incorrect argument types for bookmarks.search"),
-        "Expected error thrown when trying to search with a function as an argument"
-      );
-    });
 
-    Promise.resolve().then(() => {
-      return browser.bookmarks.search({banana: "banana"});
-    }).then(expectedError, error => {
-      let substr = `an unexpected "banana" property`;
-      browser.test.assertTrue(
-        error.message.includes(substr),
-        `Expected error ${JSON.stringify(error.message)} to contain ${JSON.stringify(substr)}`);
-    });
-
-    Promise.resolve().then(() => {
-      return browser.bookmarks.search({url: "spider-man vs. batman"});
-    }).then(expectedError, error => {
-      let substr = 'must match the format "url"';
-      browser.test.assertTrue(
-        error.message.includes(substr),
-        `Expected error ${JSON.stringify(error.message)} to contain ${JSON.stringify(substr)}`);
-    });
-
-    // queries the full url
-    return browser.bookmarks.search("http://example.org/");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for url search");
-    checkBookmark({title: "Example", url: "http://example.org/", index: 2}, results[0]);
+      return browser.bookmarks.get([nonExistentId]).then(expectedError, nonExistentIdError => {
+        browser.test.assertTrue(
+          nonExistentIdError.message.includes("Bookmark not found"),
+          "Expected error thrown when trying to get a bookmark using a non-existent Id"
+        );
+      });
+    }).then(() => {
+      return browser.bookmarks.search({});
+    }).then(results => {
+      initialBookmarkCount = results.length;
+      return browser.bookmarks.create({title: "test bookmark", url: "http://example.org"});
+    }).then(result => {
+      ourId = result.id;
+      checkOurBookmark(result);
+      browser.test.assertEq(1, collectedEvents.length, "1 expected event received");
+      checkOnCreated(ourId, bookmarkGuids.unfiledGuid, 0, "test bookmark", "http://example.org/", result.dateAdded);
 
-    // queries a partial url
-    return browser.bookmarks.search("example.org");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for url search");
-    checkBookmark({title: "Example", url: "http://example.org/", index: 2}, results[0]);
+      return browser.bookmarks.get(ourId);
+    }).then(results => {
+      browser.test.assertEq(results.length, 1);
+      checkOurBookmark(results[0]);
 
-    // queries the title
-    return browser.bookmarks.search("EFF");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for title search");
-    checkBookmark({title: "EFF", url: "http://eff.org/", index: 0, parentId: bookmarkGuids.unfiledGuid}, results[0]);
-
-    // finds menu items
-    return browser.bookmarks.search("Menu Item");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for menu item search");
-    checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 0, parentId: bookmarkGuids.menuGuid}, results[0]);
+      unsortedId = results[0].parentId;
+      return browser.bookmarks.get(unsortedId);
+    }).then(results => {
+      let folder = results[0];
+      browser.test.assertEq(1, results.length, "1 bookmark was returned");
 
-    // finds toolbar items
-    return browser.bookmarks.search("Toolbar Item");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for toolbar item search");
-    checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 0, parentId: bookmarkGuids.toolbarGuid}, results[0]);
-
-    // finds folders
-    return browser.bookmarks.search("Mozilla Folder");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of folders returned");
-    browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
+      browser.test.assertEq(unsortedId, folder.id, "Folder has the expected id");
+      browser.test.assertTrue("parentId" in folder, "Folder has a parentId");
+      browser.test.assertTrue("index" in folder, "Folder has an index");
+      browser.test.assertFalse("url" in folder, "Folder does not have a url");
+      browser.test.assertEq("Other Bookmarks", folder.title, "Folder has the expected title");
+      browser.test.assertTrue("dateAdded" in folder, "Folder has a dateAdded");
+      browser.test.assertTrue("dateGroupModified" in folder, "Folder has a dateGroupModified");
+      browser.test.assertFalse("unmodifiable" in folder, "Folder is not unmodifiable"); // TODO: Do we want to enable this?
 
-    // is case-insensitive
-    return browser.bookmarks.search("corporation");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returnedfor case-insensitive search");
-    browser.test.assertEq("Mozilla Corporation", results[0].title, "Bookmark has the expected title");
-
-    // is case-insensitive for non-ascii
-    return browser.bookmarks.search("MøZILLÄ");
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for non-ascii search");
-    browser.test.assertEq("MØzillä", results[0].title, "Bookmark has the expected title");
+      return browser.bookmarks.getChildren(unsortedId);
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "The folder has one child");
+      checkOurBookmark(results[0]);
 
-    // returns multiple results
-    return browser.bookmarks.search("allizom");
-  }).then(results => {
-    browser.test.assertEq(4, results.length, "Expected number of multiple results returned");
-    browser.test.assertEq("Mozilla", results[0].title, "Bookmark has the expected title");
-    browser.test.assertEq("Mozilla Corporation", results[1].title, "Bookmark has the expected title");
-    browser.test.assertEq("Firefox", results[2].title, "Bookmark has the expected title");
-    browser.test.assertEq("About Mozilla", results[3].title, "Bookmark has the expected title");
-
-    // accepts a url field
-    return browser.bookmarks.search({url: "http://allizom.com/"});
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for url field");
-    checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+      return browser.bookmarks.update(nonExistentId, {title: "new test title"}).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("No bookmarks found for the provided GUID"),
+          "Expected error thrown when trying to update a non-existent bookmark"
+        );
 
-    // normalizes urls
-    return browser.bookmarks.search({url: "http://allizom.com"});
-  }).then(results => {
-    browser.test.assertEq(results.length, 1, "Expected number of results returned for normalized url field");
-    checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
-
-    // normalizes urls even more
-    return browser.bookmarks.search({url: "http:allizom.com"});
-  }).then(results => {
-    browser.test.assertEq(results.length, 1, "Expected number of results returned for normalized url field");
-    checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+        return browser.bookmarks.update(ourId, {title: "new test title", url: "http://example.com/"});
+      });
+    }).then(result => {
+      browser.test.assertEq("new test title", result.title, "Updated bookmark has the expected title");
+      browser.test.assertEq("http://example.com/", result.url, "Updated bookmark has the expected URL");
+      browser.test.assertEq(ourId, result.id, "Updated bookmark has the expected id");
 
-    // accepts a title field
-    return browser.bookmarks.search({title: "Mozilla"});
-  }).then(results => {
-    browser.test.assertEq(results.length, 1, "Expected number of results returned for title field");
-    checkBookmark({title: "Mozilla", url: "http://allizom.org/", index: 3}, results[0]);
-
-    // can combine title and query
-    return browser.bookmarks.search({title: "Mozilla", query: "allizom"});
-  }).then(results => {
-    browser.test.assertEq(1, results.length, "Expected number of results returned for title and query fields");
-    checkBookmark({title: "Mozilla", url: "http://allizom.org/", index: 3}, results[0]);
+      browser.test.assertEq(2, collectedEvents.length, "2 expected events received");
+      checkOnChanged(ourId, "http://example.com/", "new test title");
 
-    // uses AND conditions
-    return browser.bookmarks.search({title: "EFF", query: "allizom"});
-  }).then(results => {
-    browser.test.assertEq(
-      0,
-      results.length,
-      "Expected number of results returned for non-matching title and query fields"
-    );
-
-    // returns an empty array on item not found
-    return browser.bookmarks.search("microsoft");
-  }).then(results => {
-    browser.test.assertEq(0, results.length, "Expected number of results returned for non-matching search");
-
-    return Promise.resolve().then(() => {
-      return browser.bookmarks.getRecent("");
-    }).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Incorrect argument types for bookmarks.getRecent"),
-        "Expected error thrown when calling getRecent with an empty string"
+      return Promise.resolve().then(() => {
+        return browser.bookmarks.update(ourId, {url: "this is not a valid url"});
+      }).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Invalid bookmark:"),
+          "Expected error thrown when trying update with an invalid url"
+        );
+        return browser.bookmarks.getTree();
+      });
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "getTree returns one result");
+      let bookmark = results[0].children.find(bookmarkItem => bookmarkItem.id == unsortedId);
+      browser.test.assertEq(
+          "Other Bookmarks",
+          bookmark.title,
+          "Folder returned from getTree has the expected title"
       );
-    });
-  }).then(() => {
-    return Promise.resolve().then(() => {
-      return browser.bookmarks.getRecent(1.234);
-    }).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("Incorrect argument types for bookmarks.getRecent"),
-        "Expected error thrown when calling getRecent with a decimal number"
-      );
-    });
-  }).then(() => {
-    return Promise.all([
-      browser.bookmarks.search("corporation"),
-      browser.bookmarks.getChildren(bookmarkGuids.menuGuid),
-    ]);
-  }).then(results => {
-    let corporationBookmark = results[0][0];
-    let childCount = results[1].length;
 
-    browser.test.assertEq(2, corporationBookmark.index, "Bookmark has the expected index");
-
-    return browser.bookmarks.move(corporationBookmark.id, {index: 0}).then(result => {
-      browser.test.assertEq(0, result.index, "Bookmark has the expected index");
+      return browser.bookmarks.create({parentId: "invalid"}).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Invalid bookmark"),
+          "Expected error thrown when trying to create a bookmark with an invalid parentId"
+        );
+        browser.test.assertTrue(
+            error.message.includes(`"parentGuid":"invalid"`),
+            "Expected error thrown when trying to create a bookmark with an invalid parentId"
+        );
+      });
+    }).then(() => {
+      return browser.bookmarks.remove(ourId);
+    }).then(result => {
+      browser.test.assertEq(undefined, result, "Removing a bookmark returns undefined");
 
       browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnMoved(corporationBookmark.id, createdFolderId, createdFolderId, 0, 2);
+      checkOnRemoved(ourId, bookmarkGuids.unfiledGuid, 0, "http://example.com/");
+
+      return browser.bookmarks.get(ourId).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Bookmark not found"),
+          "Expected error thrown when trying to get a removed bookmark"
+        );
+      });
+    }).then(() => {
+      return browser.bookmarks.remove(nonExistentId).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("No bookmarks found for the provided GUID"),
+          "Expected error thrown when trying removed a non-existent bookmark"
+        );
+      });
+    }).then(() => {
+      // test bookmarks.search
+      return Promise.all([
+        browser.bookmarks.create({title: "MØzillä", url: "http://møzîllä.örg/"}),
+        browser.bookmarks.create({title: "Example", url: "http://example.org/"}),
+        browser.bookmarks.create({title: "Mozilla Folder"}),
+        browser.bookmarks.create({title: "EFF", url: "http://eff.org/"}),
+        browser.bookmarks.create({title: "Menu Item", url: "http://menu.org/", parentId: bookmarkGuids.menuGuid}),
+        browser.bookmarks.create({title: "Toolbar Item", url: "http://toolbar.org/", parentId: bookmarkGuids.toolbarGuid}),
+      ]);
+    }).then(results => {
+      browser.test.assertEq(6, collectedEvents.length, "6 expected events received");
+      checkOnCreated(results[5].id, bookmarkGuids.toolbarGuid, 0, "Toolbar Item", "http://toolbar.org/", results[5].dateAdded);
+      checkOnCreated(results[4].id, bookmarkGuids.menuGuid, 0, "Menu Item", "http://menu.org/", results[4].dateAdded);
+      checkOnCreated(results[3].id, bookmarkGuids.unfiledGuid, 0, "EFF", "http://eff.org/", results[3].dateAdded);
+      checkOnCreated(results[2].id, bookmarkGuids.unfiledGuid, 0, "Mozilla Folder", undefined, results[2].dateAdded);
+      checkOnCreated(results[1].id, bookmarkGuids.unfiledGuid, 0, "Example", "http://example.org/", results[1].dateAdded);
+      checkOnCreated(results[0].id, bookmarkGuids.unfiledGuid, 0, "MØzillä", "http://møzîllä.örg/", results[0].dateAdded);
+
+      for (let result of results) {
+        if (result.title !== "Mozilla Folder") {
+          createdBookmarks.add(result.id);
+        }
+      }
+      let folderResult = results[2];
+      createdFolderId = folderResult.id;
+      return Promise.all([
+        browser.bookmarks.create({title: "Mozilla", url: "http://allizom.org/", parentId: createdFolderId}),
+        browser.bookmarks.create({title: "Mozilla Corporation", url: "http://allizom.com/", parentId: createdFolderId}),
+        browser.bookmarks.create({title: "Firefox", url: "http://allizom.org/firefox/", parentId: createdFolderId}),
+      ]).then(newBookmarks => {
+        browser.test.assertEq(3, collectedEvents.length, "3 expected events received");
+        checkOnCreated(newBookmarks[2].id, createdFolderId, 0, "Firefox", "http://allizom.org/firefox/", newBookmarks[2].dateAdded);
+        checkOnCreated(newBookmarks[1].id, createdFolderId, 0, "Mozilla Corporation", "http://allizom.com/", newBookmarks[1].dateAdded);
+        checkOnCreated(newBookmarks[0].id, createdFolderId, 0, "Mozilla", "http://allizom.org/", newBookmarks[0].dateAdded);
+
+        return browser.bookmarks.create({
+          title: "About Mozilla",
+          url: "http://allizom.org/about/",
+          parentId: createdFolderId,
+          index: 1,
+        });
+      }).then(result => {
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnCreated(result.id, createdFolderId, 1, "About Mozilla", "http://allizom.org/about/", result.dateAdded);
+
+        // returns all items on empty object
+        return browser.bookmarks.search({});
+      }).then(bookmarksSearchResults => {
+        browser.test.assertTrue(bookmarksSearchResults.length >= 9, "At least as many bookmarks as added were returned by search({})");
 
-      return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.menuGuid});
-    }).then(result => {
-      browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
-      browser.test.assertEq(childCount, result.index, "Bookmark has the expected index");
+        return Promise.resolve().then(() => {
+          return browser.bookmarks.remove(createdFolderId);
+        }).then(expectedError, error => {
+          browser.test.assertTrue(
+            error.message.includes("Cannot remove a non-empty folder"),
+            "Expected error thrown when trying to remove a non-empty folder"
+          );
+          return browser.bookmarks.getSubTree(createdFolderId);
+        });
+      });
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of nodes returned by getSubTree");
+      browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
+      let children = results[0].children;
+      browser.test.assertEq(4, children.length, "Expected number of bookmarks returned by getSubTree");
+      browser.test.assertEq("Firefox", children[0].title, "Bookmark has the expected title");
+      browser.test.assertEq("About Mozilla", children[1].title, "Bookmark has the expected title");
+      browser.test.assertEq(1, children[1].index, "Bookmark has the expected index");
+      browser.test.assertEq("Mozilla Corporation", children[2].title, "Bookmark has the expected title");
+      browser.test.assertEq("Mozilla", children[3].title, "Bookmark has the expected title");
+
+      // throws an error for invalid query objects
+      Promise.resolve().then(() => {
+        return browser.bookmarks.search();
+      }).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Incorrect argument types for bookmarks.search"),
+          "Expected error thrown when trying to search with no arguments"
+        );
+      });
+
+      Promise.resolve().then(() => {
+        return browser.bookmarks.search(null);
+      }).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Incorrect argument types for bookmarks.search"),
+          "Expected error thrown when trying to search with null as an argument"
+        );
+      });
 
-      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, createdFolderId, 1, 0);
+      Promise.resolve().then(() => {
+        return browser.bookmarks.search(function() {});
+      }).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Incorrect argument types for bookmarks.search"),
+          "Expected error thrown when trying to search with a function as an argument"
+        );
+      });
+
+      Promise.resolve().then(() => {
+        return browser.bookmarks.search({banana: "banana"});
+      }).then(expectedError, error => {
+        let substr = `an unexpected "banana" property`;
+        browser.test.assertTrue(
+          error.message.includes(substr),
+          `Expected error ${JSON.stringify(error.message)} to contain ${JSON.stringify(substr)}`);
+      });
+
+      Promise.resolve().then(() => {
+        return browser.bookmarks.search({url: "spider-man vs. batman"});
+      }).then(expectedError, error => {
+        let substr = 'must match the format "url"';
+        browser.test.assertTrue(
+          error.message.includes(substr),
+          `Expected error ${JSON.stringify(error.message)} to contain ${JSON.stringify(substr)}`);
+      });
+
+      // queries the full url
+      return browser.bookmarks.search("http://example.org/");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for url search");
+      checkBookmark({title: "Example", url: "http://example.org/", index: 2}, results[0]);
+
+      // queries a partial url
+      return browser.bookmarks.search("example.org");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for url search");
+      checkBookmark({title: "Example", url: "http://example.org/", index: 2}, results[0]);
+
+      // queries the title
+      return browser.bookmarks.search("EFF");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for title search");
+      checkBookmark({title: "EFF", url: "http://eff.org/", index: 0, parentId: bookmarkGuids.unfiledGuid}, results[0]);
+
+      // finds menu items
+      return browser.bookmarks.search("Menu Item");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for menu item search");
+      checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 0, parentId: bookmarkGuids.menuGuid}, results[0]);
+
+      // finds toolbar items
+      return browser.bookmarks.search("Toolbar Item");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for toolbar item search");
+      checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 0, parentId: bookmarkGuids.toolbarGuid}, results[0]);
 
-      return browser.bookmarks.move(corporationBookmark.id, {index: 0});
-    }).then(result => {
-      browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
-      browser.test.assertEq(0, result.index, "Bookmark has the expected index");
+      // finds folders
+      return browser.bookmarks.search("Mozilla Folder");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of folders returned");
+      browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
+
+      // is case-insensitive
+      return browser.bookmarks.search("corporation");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returnedfor case-insensitive search");
+      browser.test.assertEq("Mozilla Corporation", results[0].title, "Bookmark has the expected title");
+
+      // is case-insensitive for non-ascii
+      return browser.bookmarks.search("MøZILLÄ");
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for non-ascii search");
+      browser.test.assertEq("MØzillä", results[0].title, "Bookmark has the expected title");
+
+      // returns multiple results
+      return browser.bookmarks.search("allizom");
+    }).then(results => {
+      browser.test.assertEq(4, results.length, "Expected number of multiple results returned");
+      browser.test.assertEq("Mozilla", results[0].title, "Bookmark has the expected title");
+      browser.test.assertEq("Mozilla Corporation", results[1].title, "Bookmark has the expected title");
+      browser.test.assertEq("Firefox", results[2].title, "Bookmark has the expected title");
+      browser.test.assertEq("About Mozilla", results[3].title, "Bookmark has the expected title");
+
+      // accepts a url field
+      return browser.bookmarks.search({url: "http://allizom.com/"});
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for url field");
+      checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+
+      // normalizes urls
+      return browser.bookmarks.search({url: "http://allizom.com"});
+    }).then(results => {
+      browser.test.assertEq(results.length, 1, "Expected number of results returned for normalized url field");
+      checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+
+      // normalizes urls even more
+      return browser.bookmarks.search({url: "http:allizom.com"});
+    }).then(results => {
+      browser.test.assertEq(results.length, 1, "Expected number of results returned for normalized url field");
+      checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+
+      // accepts a title field
+      return browser.bookmarks.search({title: "Mozilla"});
+    }).then(results => {
+      browser.test.assertEq(results.length, 1, "Expected number of results returned for title field");
+      checkBookmark({title: "Mozilla", url: "http://allizom.org/", index: 3}, results[0]);
+
+      // can combine title and query
+      return browser.bookmarks.search({title: "Mozilla", query: "allizom"});
+    }).then(results => {
+      browser.test.assertEq(1, results.length, "Expected number of results returned for title and query fields");
+      checkBookmark({title: "Mozilla", url: "http://allizom.org/", index: 3}, results[0]);
+
+      // uses AND conditions
+      return browser.bookmarks.search({title: "EFF", query: "allizom"});
+    }).then(results => {
+      browser.test.assertEq(
+        0,
+        results.length,
+        "Expected number of results returned for non-matching title and query fields"
+      );
+
+      // returns an empty array on item not found
+      return browser.bookmarks.search("microsoft");
+    }).then(results => {
+      browser.test.assertEq(0, results.length, "Expected number of results returned for non-matching search");
 
-      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, bookmarkGuids.menuGuid, 0, 1);
+      return Promise.resolve().then(() => {
+        return browser.bookmarks.getRecent("");
+      }).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Incorrect argument types for bookmarks.getRecent"),
+          "Expected error thrown when calling getRecent with an empty string"
+        );
+      });
+    }).then(() => {
+      return Promise.resolve().then(() => {
+        return browser.bookmarks.getRecent(1.234);
+      }).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("Incorrect argument types for bookmarks.getRecent"),
+          "Expected error thrown when calling getRecent with a decimal number"
+        );
+      });
+    }).then(() => {
+      return Promise.all([
+        browser.bookmarks.search("corporation"),
+        browser.bookmarks.getChildren(bookmarkGuids.menuGuid),
+      ]);
+    }).then(results => {
+      let corporationBookmark = results[0][0];
+      let childCount = results[1].length;
+
+      browser.test.assertEq(2, corporationBookmark.index, "Bookmark has the expected index");
+
+      return browser.bookmarks.move(corporationBookmark.id, {index: 0}).then(result => {
+        browser.test.assertEq(0, result.index, "Bookmark has the expected index");
+
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnMoved(corporationBookmark.id, createdFolderId, createdFolderId, 0, 2);
+
+        return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.menuGuid});
+      }).then(result => {
+        browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
+        browser.test.assertEq(childCount, result.index, "Bookmark has the expected index");
+
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, createdFolderId, 1, 0);
+
+        return browser.bookmarks.move(corporationBookmark.id, {index: 0});
+      }).then(result => {
+        browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
+        browser.test.assertEq(0, result.index, "Bookmark has the expected index");
 
-      return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.toolbarGuid, index: 1});
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, bookmarkGuids.menuGuid, 0, 1);
+
+        return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.toolbarGuid, index: 1});
+      }).then(result => {
+        browser.test.assertEq(bookmarkGuids.toolbarGuid, result.parentId, "Bookmark has the expected parent");
+        browser.test.assertEq(1, result.index, "Bookmark has the expected index");
+
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnMoved(corporationBookmark.id, bookmarkGuids.toolbarGuid, bookmarkGuids.menuGuid, 1, 0);
+
+        createdBookmarks.add(corporationBookmark.id);
+      });
+    }).then(() => {
+      return browser.bookmarks.getRecent(4);
+    }).then(results => {
+      browser.test.assertEq(4, results.length, "Expected number of results returned by getRecent");
+      let prevDate = results[0].dateAdded;
+      for (let bookmark of results) {
+        browser.test.assertTrue(bookmark.dateAdded <= prevDate, "The recent bookmarks are sorted by dateAdded");
+        prevDate = bookmark.dateAdded;
+      }
+      let bookmarksByTitle = results.sort((a, b) => {
+        return a.title.localeCompare(b.title);
+      });
+      browser.test.assertEq("About Mozilla", bookmarksByTitle[0].title, "Bookmark has the expected title");
+      browser.test.assertEq("Firefox", bookmarksByTitle[1].title, "Bookmark has the expected title");
+      browser.test.assertEq("Mozilla", bookmarksByTitle[2].title, "Bookmark has the expected title");
+      browser.test.assertEq("Mozilla Corporation", bookmarksByTitle[3].title, "Bookmark has the expected title");
+
+      return browser.bookmarks.search({});
+    }).then(results => {
+      let startBookmarkCount = results.length;
+
+      return browser.bookmarks.search({title: "Mozilla Folder"}).then(result => {
+        return browser.bookmarks.removeTree(result[0].id);
+      }).then(() => {
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnRemoved(createdFolderId, bookmarkGuids.unfiledGuid, 1);
+
+        return browser.bookmarks.search({}).then(searchResults => {
+          browser.test.assertEq(
+            startBookmarkCount - 4,
+            searchResults.length,
+            "Expected number of results returned after removeTree");
+        });
+      });
+    }).then(() => {
+      return browser.bookmarks.create({title: "Empty Folder"});
     }).then(result => {
-      browser.test.assertEq(bookmarkGuids.toolbarGuid, result.parentId, "Bookmark has the expected parent");
-      browser.test.assertEq(1, result.index, "Bookmark has the expected index");
+      let emptyFolderId = result.id;
 
       browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnMoved(corporationBookmark.id, bookmarkGuids.toolbarGuid, bookmarkGuids.menuGuid, 1, 0);
+      checkOnCreated(emptyFolderId, bookmarkGuids.unfiledGuid, 3, "Empty Folder", undefined, result.dateAdded);
+
+      browser.test.assertEq("Empty Folder", result.title, "Folder has the expected title");
+      return browser.bookmarks.remove(emptyFolderId).then(() => {
+        browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+        checkOnRemoved(emptyFolderId, bookmarkGuids.unfiledGuid, 3);
 
-      createdBookmarks.add(corporationBookmark.id);
-    });
-  }).then(() => {
-    return browser.bookmarks.getRecent(4);
-  }).then(results => {
-    browser.test.assertEq(4, results.length, "Expected number of results returned by getRecent");
-    let prevDate = results[0].dateAdded;
-    for (let bookmark of results) {
-      browser.test.assertTrue(bookmark.dateAdded <= prevDate, "The recent bookmarks are sorted by dateAdded");
-      prevDate = bookmark.dateAdded;
-    }
-    let bookmarksByTitle = results.sort((a, b) => {
-      return a.title.localeCompare(b.title);
-    });
-    browser.test.assertEq("About Mozilla", bookmarksByTitle[0].title, "Bookmark has the expected title");
-    browser.test.assertEq("Firefox", bookmarksByTitle[1].title, "Bookmark has the expected title");
-    browser.test.assertEq("Mozilla", bookmarksByTitle[2].title, "Bookmark has the expected title");
-    browser.test.assertEq("Mozilla Corporation", bookmarksByTitle[3].title, "Bookmark has the expected title");
-
-    return browser.bookmarks.search({});
-  }).then(results => {
-    let startBookmarkCount = results.length;
-
-    return browser.bookmarks.search({title: "Mozilla Folder"}).then(result => {
-      return browser.bookmarks.removeTree(result[0].id);
+        return browser.bookmarks.get(emptyFolderId).then(expectedError, error => {
+          browser.test.assertTrue(
+            error.message.includes("Bookmark not found"),
+            "Expected error thrown when trying to get a removed folder"
+          );
+        });
+      });
     }).then(() => {
-      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnRemoved(createdFolderId, bookmarkGuids.unfiledGuid, 1);
-
-      return browser.bookmarks.search({}).then(searchResults => {
-        browser.test.assertEq(
-          startBookmarkCount - 4,
-          searchResults.length,
-          "Expected number of results returned after removeTree");
+      return browser.bookmarks.getChildren(nonExistentId).then(expectedError, error => {
+        browser.test.assertTrue(
+          error.message.includes("root is null"),
+          "Expected error thrown when trying to getChildren for a non-existent folder"
+        );
       });
-    });
-  }).then(() => {
-    return browser.bookmarks.create({title: "Empty Folder"});
-  }).then(result => {
-    let emptyFolderId = result.id;
-
-    browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-    checkOnCreated(emptyFolderId, bookmarkGuids.unfiledGuid, 3, "Empty Folder", undefined, result.dateAdded);
-
-    browser.test.assertEq("Empty Folder", result.title, "Folder has the expected title");
-    return browser.bookmarks.remove(emptyFolderId).then(() => {
-      browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
-      checkOnRemoved(emptyFolderId, bookmarkGuids.unfiledGuid, 3);
-
-      return browser.bookmarks.get(emptyFolderId).then(expectedError, error => {
+    }).then(() => {
+      return Promise.resolve().then(() => {
+        return browser.bookmarks.move(nonExistentId, {});
+      }).then(expectedError, error => {
         browser.test.assertTrue(
-          error.message.includes("Bookmark not found"),
-          "Expected error thrown when trying to get a removed folder"
+          error.message.includes("No bookmarks found for the provided GUID"),
+          "Expected error thrown when calling move with a non-existent bookmark"
         );
       });
-    });
-  }).then(() => {
-    return browser.bookmarks.getChildren(nonExistentId).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("root is null"),
-        "Expected error thrown when trying to getChildren for a non-existent folder"
-      );
+    }).then(() => {
+      // remove all created bookmarks
+      let promises = Array.from(createdBookmarks, guid => browser.bookmarks.remove(guid));
+      return Promise.all(promises);
+    }).then(() => {
+      browser.test.assertEq(createdBookmarks.size, collectedEvents.length, "expected number of events received");
+
+      return browser.bookmarks.search({});
+    }).then(results => {
+      browser.test.assertEq(initialBookmarkCount, results.length, "All created bookmarks have been removed");
+
+      return browser.test.notifyPass("bookmarks");
+    }).catch(error => {
+      browser.test.fail(`Error: ${String(error)} :: ${error.stack}`);
+      browser.test.notifyFail("bookmarks");
     });
-  }).then(() => {
-    return Promise.resolve().then(() => {
-      return browser.bookmarks.move(nonExistentId, {});
-    }).then(expectedError, error => {
-      browser.test.assertTrue(
-        error.message.includes("No bookmarks found for the provided GUID"),
-        "Expected error thrown when calling move with a non-existent bookmark"
-      );
+  }
+
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["bookmarks"],
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("bookmarks");
+  await extension.unload();
+});
+
+add_task(async function test_get_recent_with_tag_and_query() {
+  function background() {
+    browser.bookmarks.getRecent(100).then(bookmarks => {
+      browser.test.sendMessage("bookmarks", bookmarks);
     });
-  }).then(() => {
-    // remove all created bookmarks
-    let promises = Array.from(createdBookmarks, guid => browser.bookmarks.remove(guid));
-    return Promise.all(promises);
-  }).then(() => {
-    browser.test.assertEq(createdBookmarks.size, collectedEvents.length, "expected number of events received");
+  }
 
-    return browser.bookmarks.search({});
-  }).then(results => {
-    browser.test.assertEq(initialBookmarkCount, results.length, "All created bookmarks have been removed");
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["bookmarks"],
+    },
+  });
+
+  // Start with an empty bookmarks database.
+  await PlacesUtils.bookmarks.eraseEverything();
 
-    return browser.test.notifyPass("bookmarks");
-  }).catch(error => {
-    browser.test.fail(`Error: ${String(error)} :: ${error.stack}`);
-    browser.test.notifyFail("bookmarks");
-  });
-}
+  let createdBookmarks = [];
+  for (let i = 0; i < 3; i++) {
+    let bookmark = {
+      type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+      url: `http://example.com/${i}`,
+      title: `My bookmark ${i}`,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    };
+    createdBookmarks.unshift(bookmark);
+    await PlacesUtils.bookmarks.insert(bookmark);
+  }
 
-let extensionData = {
-  background: `(${backgroundScript})()`,
-  manifest: {
-    permissions: ["bookmarks"],
-  },
-};
+  // Add a tag to the most recent url to prove it doesn't get returned.
+  PlacesUtils.tagging.tagURI(NetUtil.newURI("http://example.com/${i}"), ["Test Tag"]);
+
+  // Add a query bookmark.
+  let queryURL = `place:folder=${PlacesUtils.bookmarksMenuFolderId}&queryType=1`;
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: queryURL,
+    title: "a test query"});
 
-add_task(function* test_contentscript() {
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-  yield extension.startup();
-  yield extension.awaitFinish("bookmarks");
-  yield extension.unload();
+  await extension.startup();
+  let receivedBookmarks = await extension.awaitMessage("bookmarks");
+
+  equal(receivedBookmarks.length, 3, "The expected number of bookmarks was returned.");
+  for (let i = 0; i < 3; i++) {
+    let actual = receivedBookmarks[i];
+    let expected = createdBookmarks[i];
+    equal(actual.url, expected.url, "Bookmark has the expected url.");
+    equal(actual.title, expected.title, "Bookmark has the expected title.");
+    equal(actual.parentId, expected.parentGuid, "Bookmark has the expected parentId.");
+  }
+
+  await extension.unload();
 });
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -14,21 +14,17 @@ Cu.import("resource://gre/modules/NetUti
 
 const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
 const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
 
 function LOG(str) {
   let prefB = Cc["@mozilla.org/preferences-service;1"].
               getService(Ci.nsIPrefBranch);
 
-  let shouldLog = false;
-  try {
-    shouldLog = prefB.getBoolPref("feeds.log");
-  } catch (ex) {
-  }
+  let shouldLog = prefB.getBoolPref("feeds.log", false);
 
   if (shouldLog)
     dump("*** Feeds: " + str + "\n");
 }
 
 /**
  * Wrapper function for nsIIOService::newURI.
  * @param aURLSpec
@@ -692,20 +688,17 @@ FeedWriter.prototype = {
 
   _getWebHandlerElementsForURL(aURL) {
     let menu = this._document.getElementById("handlersMenuList");
     return menu.querySelectorAll('[webhandlerurl="' + aURL + '"]');
   },
 
   _setSelectedHandler(feedType) {
     let prefs = Services.prefs;
-    let handler = "bookmarks";
-    try {
-      handler = prefs.getCharPref(getPrefReaderForType(feedType));
-    } catch (ex) { }
+    let handler = prefs.getCharPref(getPrefReaderForType(feedType), "bookmarks");
 
     switch (handler) {
       case "web": {
         if (this._handlersList) {
           let url;
           try {
             url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
           } catch (ex) {
@@ -830,20 +823,17 @@ FeedWriter.prototype = {
     // in the list is changed
     handlersList.addEventListener("change", this);
 
     // Set up the "Subscribe Now" button
     this._document.getElementById("subscribeButton")
         .addEventListener("click", this);
 
     // first-run ui
-    let showFirstRunUI = true;
-    try {
-      showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
-    } catch (ex) { }
+    let showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI, true);
     if (showFirstRunUI) {
       let textfeedinfo1, textfeedinfo2;
       switch (feedType) {
         case Ci.nsIFeed.TYPE_VIDEO:
           textfeedinfo1 = "feedSubscriptionVideoPodcast1";
           textfeedinfo2 = "feedSubscriptionVideoPodcast2";
           break;
         case Ci.nsIFeed.TYPE_AUDIO:
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -178,22 +178,19 @@ const Utils = {
       // This is handled internally, so we don't want them to register
       throw this.getSecurityError(
         `Permission denied to add ${aURIString} as a protocol handler`,
         aWindowOrNull);
     }
 
     // check if it is in the black list
     let pb = Services.prefs;
-    let allowed;
-    try {
-      allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
-    } catch (e) {
-      allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
-    }
+    let allowed =
+      pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol,
+                     pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default"));
     if (!allowed) {
       throw this.getSecurityError(
         `Not allowed to register a protocol handler for ${aProtocol}`,
         aWindowOrNull);
     }
   },
 
   // Return a SecurityError exception from the given Window if one is given.  If
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -93,30 +93,24 @@ const OVERRIDE_NEW_BUILD_ID = 3;
  *  OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
  *  OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
  *                      Gecko milestone (i.e. right after an upgrade).
  *  OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
  *                        same Gecko milestone (i.e. after a nightly upgrade).
  *  OVERRIDE_NONE otherwise.
  */
 function needHomepageOverride(prefb) {
-  var savedmstone = null;
-  try {
-    savedmstone = prefb.getCharPref("browser.startup.homepage_override.mstone");
-  } catch (e) {}
+  var savedmstone = prefb.getCharPref("browser.startup.homepage_override.mstone", "");
 
   if (savedmstone == "ignore")
     return OVERRIDE_NONE;
 
   var mstone = Services.appinfo.platformVersion;
 
-  var savedBuildID = null;
-  try {
-    savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID");
-  } catch (e) {}
+  var savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID", "");
 
   var buildID = Services.appinfo.platformBuildID;
 
   if (mstone != savedmstone) {
     // Bug 462254. Previous releases had a default pref to suppress the EULA
     // agreement if the platform's installer had already shown one. Now with
     // about:rights we've removed the EULA stuff and default pref, but we need
     // a way to make existing profiles retain the default that we removed.
@@ -479,20 +473,17 @@ nsBrowserContentHandler.prototype = {
     var additionalPage = "";
     var willRestoreSession = false;
     try {
       // Read the old value of homepage_override.mstone before
       // needHomepageOverride updates it, so that we can later add it to the
       // URL if we do end up showing an overridePage. This makes it possible
       // to have the overridePage's content vary depending on the version we're
       // upgrading from.
-      let old_mstone = "unknown";
-      try {
-        old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
-      } catch (ex) {}
+      let old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone", "unknown");
       override = needHomepageOverride(prefb);
       if (override != OVERRIDE_NONE) {
         switch (override) {
           case OVERRIDE_NEW_PROFILE:
             // New profile.
             overridePage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url");
             additionalPage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url.additional");
             // Turn on 'later run' pages for new profiles.
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -930,20 +930,17 @@ BrowserGlue.prototype = {
       }
     });
 
     this._trackSlowStartup();
 
     // Offer to reset a user's profile if it hasn't been used for 60 days.
     const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000;
     let lastUse = Services.appinfo.replacedLockTime;
-    let disableResetPrompt = false;
-    try {
-      disableResetPrompt = Services.prefs.getBoolPref("browser.disableResetPrompt");
-    } catch (e) {}
+    let disableResetPrompt = Services.prefs.getBoolPref("browser.disableResetPrompt", false);
 
     if (!disableResetPrompt && lastUse &&
         Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS) {
       this._resetProfileNotification("unused");
     } else if (AppConstants.platform == "win" && !disableResetPrompt) {
       // Check if we were just re-installed and offer Firefox Reset
       let updateChannel;
       try {
@@ -1451,20 +1448,17 @@ BrowserGlue.prototype = {
     try {
       importBookmarksHTML =
         Services.prefs.getBoolPref("browser.places.importBookmarksHTML");
       if (importBookmarksHTML)
         importBookmarks = true;
     } catch (ex) {}
 
     // Support legacy bookmarks.html format for apps that depend on that format.
-    let autoExportHTML = false;
-    try {
-      autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
-    } catch (ex) {} // Do not export.
+    let autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML", false); // Do not export.
     if (autoExportHTML) {
       // Sqlite.jsm and Places shutdown happen at profile-before-change, thus,
       // to be on the safe side, this should run earlier.
       AsyncShutdown.profileChangeTeardown.addBlocker(
         "Places: export bookmarks.html",
         () => BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath));
     }
 
@@ -1521,20 +1515,17 @@ BrowserGlue.prototype = {
           yield this.ensurePlacesDefaultQueriesInitialized();
         } catch (e) {
           Cu.reportError(e);
         }
       } else {
         // An import operation is about to run.
         // Don't try to recreate smart bookmarks if autoExportHTML is true or
         // smart bookmarks are disabled.
-        let smartBookmarksVersion = 0;
-        try {
-          smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion");
-        } catch (ex) {}
+        let smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion", 0);
         if (!autoExportHTML && smartBookmarksVersion != -1)
           Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
 
         let bookmarksUrl = null;
         if (restoreDefaultBookmarks) {
           // User wants to restore bookmarks.html file from default profile folder
           bookmarksUrl = "chrome://browser/locale/bookmarks.html";
         } else if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
@@ -1871,20 +1862,17 @@ BrowserGlue.prototype = {
         }
       } catch (ex) {}
     }
 
     if (currentUIVersion < 26) {
       // Refactor urlbar suggestion preferences to make it extendable and
       // allow new suggestion types (e.g: search suggestions).
       let types = ["history", "bookmark", "openpage"];
-      let defaultBehavior = 0;
-      try {
-        defaultBehavior = Services.prefs.getIntPref("browser.urlbar.default.behavior");
-      } catch (ex) {}
+      let defaultBehavior = Services.prefs.getIntPref("browser.urlbar.default.behavior", 0);
       try {
         let autocompleteEnabled = Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled");
         if (!autocompleteEnabled) {
           defaultBehavior = -1;
         }
       } catch (ex) {}
 
       // If the default behavior is:
@@ -2013,20 +2001,17 @@ BrowserGlue.prototype = {
 
     if (currentUIVersion < 42) {
       let backupFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
       backupFile.append("tabgroups-session-backup.json");
       OS.File.remove(backupFile.path, {ignoreAbsent: true}).catch(ex => Cu.reportError(ex));
     }
 
     if (currentUIVersion < 43) {
-      let currentTheme = null;
-      try {
-        currentTheme = Services.prefs.getCharPref("lightweightThemes.selectedThemeID");
-      } catch (e) {}
+      let currentTheme = Services.prefs.getCharPref("lightweightThemes.selectedThemeID", "");
       if (currentTheme == "firefox-devedition@mozilla.org") {
         let newTheme = Services.prefs.getCharPref("devtools.theme") == "dark" ?
           "firefox-compact-dark@mozilla.org" : "firefox-compact-light@mozilla.org";
         Services.prefs.setCharPref("lightweightThemes.selectedThemeID", newTheme);
       }
     }
 
     // Update the migration version.
@@ -2094,20 +2079,17 @@ BrowserGlue.prototype = {
     const SMART_BOOKMARKS_VERSION = 8;
     const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
     const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
 
     // TODO bug 399268: should this be a pref?
     const MAX_RESULTS = 10;
 
     // Get current smart bookmarks version.  If not set, create them.
-    let smartBookmarksCurrentVersion = 0;
-    try {
-      smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF);
-    } catch (ex) {}
+    let smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF, 0);
 
     // If version is current, or smart bookmarks are disabled, bail out.
     if (smartBookmarksCurrentVersion == -1 ||
         smartBookmarksCurrentVersion >= SMART_BOOKMARKS_VERSION) {
       return;
     }
 
     try {
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -69,32 +69,24 @@ var gSyncPane = {
 
     Services.obs.addObserver(onReady, "weave:service:ready", false);
     window.addEventListener("unload", onUnload);
 
     xps.ensureLoaded();
   },
 
   _showLoadPage(xps) {
-    let username;
-    try {
-      username = Services.prefs.getCharPref("services.sync.username");
-    } catch (e) {}
+    let username = Services.prefs.getCharPref("services.sync.username", "");
     if (!username) {
       this.page = FXA_PAGE_LOGGED_OUT;
       return;
     }
 
     // Use cached values while we wait for the up-to-date values
-    let cachedComputerName;
-    try {
-      cachedComputerName = Services.prefs.getCharPref("services.sync.client.name");
-    } catch (e) {
-      cachedComputerName = "";
-    }
+    let cachedComputerName = Services.prefs.getCharPref("services.sync.client.name", "");
     document.getElementById("fxaEmailAddress1").textContent = username;
     this._populateComputerName(cachedComputerName);
     this.page = FXA_PAGE_LOGGED_IN;
   },
 
   _init() {
     let topics = ["weave:service:login:error",
                   "weave:service:login:finish",
@@ -246,20 +238,17 @@ var gSyncPane = {
                   .getService(Components.interfaces.nsISupports)
                   .wrappedJSObject;
 
     let displayNameLabel = document.getElementById("fxaDisplayName");
     let fxaEmailAddress1Label = document.getElementById("fxaEmailAddress1");
     fxaEmailAddress1Label.hidden = false;
     displayNameLabel.hidden = true;
 
-    let profileInfoEnabled;
-    try {
-      profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
-    } catch (ex) {}
+    let profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled", false);
 
     // determine the fxa status...
     this._showLoadPage(service);
 
     fxAccounts.getSignedInUser().then(data => {
       if (!data) {
         this.page = FXA_PAGE_LOGGED_OUT;
         return false;
--- a/browser/components/privatebrowsing/test/browser/browser.ini
+++ b/browser/components/privatebrowsing/test/browser/browser.ini
@@ -21,17 +21,16 @@ support-files =
 [browser_privatebrowsing_DownloadLastDirWithCPS.js]
 [browser_privatebrowsing_about.js]
 tags = trackingprotection
 [browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js]
 [browser_privatebrowsing_aboutSessionRestore.js]
 [browser_privatebrowsing_cache.js]
 [browser_privatebrowsing_certexceptionsui.js]
 [browser_privatebrowsing_concurrent.js]
-skip-if = e10s # Bug 1315042
 [browser_privatebrowsing_context_and_chromeFlags.js]
 [browser_privatebrowsing_crh.js]
 [browser_privatebrowsing_downloadLastDir.js]
 [browser_privatebrowsing_downloadLastDir_c.js]
 [browser_privatebrowsing_downloadLastDir_toggle.js]
 [browser_privatebrowsing_favicon.js]
 [browser_privatebrowsing_geoprompt.js]
 [browser_privatebrowsing_lastpbcontextexited.js]
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
@@ -57,17 +57,17 @@ add_task(function* test() {
   // the private storage.
   private_window.close();
   private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : false });
   private_browser = null;
   yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
   private_browser = private_window.getBrowser().selectedBrowser;
 
   private_browser.loadURI(prefix + "?action=get&name=test2");
-  yield BrowserTestUtils.browserLoaded(private_browser);
+  yield BrowserTestUtils.browserLoaded(private_browser, false, prefix + "?action=get&name=test2");
   elts = yield getElts(private_browser);
   isnot(elts[0], "value2", "public window shouldn't see cleared private storage");
   is(elts[1], "1", "public window should only see public items");
 
 
   // Making it private again should clear the storage and it shouldn't
   // be able to see the old private storage as well.
   private_window.close();
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -1790,20 +1790,18 @@ this.UITour = {
       case "appinfo":
         let props = ["defaultUpdateChannel", "version"];
         let appinfo = {};
         props.forEach(property => appinfo[property] = Services.appinfo[property]);
 
         // Identifier of the partner repack, as stored in preference "distribution.id"
         // and included in Firefox and other update pings. Note this is not the same as
         // Services.appinfo.distributionID (value of MOZ_DISTRIBUTION_ID is set at build time).
-        let distribution = "default";
-        try {
-          distribution = Services.prefs.getDefaultBranch("distribution.").getCharPref("id");
-        } catch (e) {}
+        let distribution =
+          Services.prefs.getDefaultBranch("distribution.").getCharPref("id", "default");
         appinfo["distribution"] = distribution;
 
         let isDefaultBrowser = null;
         try {
           let shell = aWindow.getShellService();
           if (shell) {
             isDefaultBrowser = shell.isDefaultBrowser(false);
           }
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -58,21 +58,20 @@
       <method name="_onUnderflow">
         <body></body>
       </method>
 
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
           let outerBoxRect = this.parentNode.getBoundingClientRect();
-          let value = this.getAttribute("ac-value");
-          let comment = this.getAttribute("ac-comment");
+          let {primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
 
-          this._comment.textContent = comment;
-          this._label.textContent = value;
+          this._label.textContent = primary;
+          this._comment.textContent = secondary;
 
           // Make item fit in popup as XUL box could not constrain
           // item's width
           this._itemBox.style.width =  outerBoxRect.width + "px";
           // Use two-lines layout when width is smaller than 150px
           if (outerBoxRect.width <= 150) {
             this._itemBox.setAttribute("size", "small");
           } else {
--- a/browser/modules/AboutNewTab.jsm
+++ b/browser/modules/AboutNewTab.jsm
@@ -19,25 +19,42 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
   "resource://gre/modules/RemotePageManager.jsm");
 
 var AboutNewTab = {
 
   pageListener: null,
 
+  isOverridden: false,
+
   init() {
+    if (this.isOverridden) {
+      return;
+    }
     this.pageListener = new RemotePages("about:newtab");
     this.pageListener.addMessageListener("NewTab:Customize", this.customize.bind(this));
     this.pageListener.addMessageListener("NewTab:MaybeShowAutoMigrationUndoNotification",
       (msg) => AutoMigrate.maybeShowUndoNotification(msg.target.browser));
   },
 
   customize(message) {
     NewTabUtils.allPages.enabled = message.data.enabled;
     NewTabUtils.allPages.enhanced = message.data.enhanced;
   },
 
   uninit() {
-    this.pageListener.destroy();
-    this.pageListener = null;
+    if (this.pageListener) {
+      this.pageListener.destroy();
+      this.pageListener = null;
+    }
   },
+
+  override() {
+    this.uninit();
+    this.isOverridden = true;
+  },
+
+  reset() {
+    this.isOverridden = false;
+    this.init();
+  }
 };
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -595,20 +595,17 @@ this.BrowserUITelemetry = {
     result.countableEvents = this._countableEvents;
     result.durations = this._durations;
     return result;
   },
 
   getSyncState() {
     let result = {};
     for (let sub of ["desktop", "mobile"]) {
-      let count = 0;
-      try {
-        count = Services.prefs.getIntPref("services.sync.clients.devices." + sub);
-      } catch (ex) {}
+      let count = Services.prefs.getIntPref("services.sync.clients.devices." + sub, 0);
       result[sub] = count;
     }
     return result;
   },
 
   countCustomizationEvent(aEventType) {
     this._countEvent(["customize", aEventType]);
   },
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -183,20 +183,17 @@ var DirectoryLinksProvider = {
     return this.__linksURL;
   },
 
   /**
    * Gets the currently selected locale for display.
    * @return  the selected locale or "en-US" if none is selected
    */
   get locale() {
-    let matchOS;
-    try {
-      matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
-    } catch (e) {}
+    let matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE, false);
 
     if (matchOS) {
       return Cc["@mozilla.org/intl/ospreferences;1"].
              getService(Ci.mozIOSPreferences).systemLocale;
     }
 
     try {
       let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
--- a/browser/modules/SocialService.jsm
+++ b/browser/modules/SocialService.jsm
@@ -206,26 +206,21 @@ var ActiveProviders = {
                  createInstance(Ci.nsISupportsString);
     string.data = JSON.stringify(this._providers);
     Services.prefs.setComplexValue("social.activeProviders",
                                    Ci.nsISupportsString, string);
   }
 };
 
 function migrateSettings() {
-  let activeProviders, enabled;
-  try {
-    activeProviders = Services.prefs.getCharPref("social.activeProviders");
-  } catch (e) {
-    // not set, we'll check if we need to migrate older prefs
-  }
+  let enabled;
   if (Services.prefs.prefHasUserValue("social.enabled")) {
     enabled = Services.prefs.getBoolPref("social.enabled");
   }
-  if (activeProviders) {
+  if (Services.prefs.getCharPref("social.activeProviders", "")) {
     // migration from fx21 to fx22 or later
     // ensure any *builtin* provider in activeproviders is in user level prefs
     for (let origin in ActiveProviders._providers) {
       let prefname;
       let manifest;
       let defaultManifest;
       try {
         prefname = getPrefnameFromOrigin(origin);
@@ -279,20 +274,17 @@ function migrateSettings() {
       }
     }
     ActiveProviders.flush();
     Services.prefs.clearUserPref("social.enabled");
     return;
   }
 
   // primary migration from pre-fx21
-  let active;
-  try {
-    active = Services.prefs.getBoolPref("social.active");
-  } catch (e) {}
+  let active = Services.prefs.getBoolPref("social.active", false);
   if (!active)
     return;
 
   // primary difference from SocialServiceInternal.manifests is that we
   // only read the default branch here.
   let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest.");
   let prefs = manifestPrefs.getChildList("", []);
   for (let pref of prefs) {
--- a/browser/modules/URLBarZoom.jsm
+++ b/browser/modules/URLBarZoom.jsm
@@ -21,42 +21,51 @@ var URLBarZoom = {
 function fullZoomObserver(aSubject, aTopic) {
   // If the tab was the last one in its window and has been dragged to another
   // window, the original browser's window will be unavailable here. Since that
   // window is closing, we can just ignore this notification.
   if (!aSubject.ownerGlobal) {
     return;
   }
 
-  // Only allow pulse animation for zoom changes, not tab switching.
   let animate = (aTopic != "browser-fullZoom:location-change");
   updateZoomButton(aSubject, animate);
 }
 
 function onEndSwapDocShells(event) {
   updateZoomButton(event.originalTarget);
 }
 
-function updateZoomButton(aBrowser, aAnimate = false) {
+  /**
+   * Updates the zoom button in the location bar.
+   *
+   * @param {object} aBrowser The browser that the zoomed content resides in.
+   * @param {boolean} aAnimate Should be True for all cases unless the zoom
+   *   change is related to tab switching. Optional
+   * @param {number} aValue The value that should be used for the zoom control.
+   *   If not provided then the value will be read from the window. Useful
+   *   if the data on the window may be stale.
+   */
+function updateZoomButton(aBrowser, aAnimate = false, aValue = undefined) {
   let win = aBrowser.ownerGlobal;
   if (aBrowser != win.gBrowser.selectedBrowser) {
     return;
   }
 
   let customizableZoomControls = win.document.getElementById("zoom-controls");
   let zoomResetButton = win.document.getElementById("urlbar-zoom-button");
 
   // Ensure that zoom controls haven't already been added to browser in Customize Mode
   if (customizableZoomControls &&
       customizableZoomControls.getAttribute("cui-areatype") == "toolbar") {
     zoomResetButton.hidden = true;
     return;
   }
 
-  let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
+  let zoomFactor = Math.round((aValue || win.ZoomManager.zoom) * 100);
   if (zoomFactor != 100) {
     // Check if zoom button is visible and update label if it is
     if (zoomResetButton.hidden) {
       zoomResetButton.hidden = false;
     }
     if (aAnimate) {
       zoomResetButton.setAttribute("animate", "true");
     } else {
@@ -68,8 +77,11 @@ function updateZoomButton(aBrowser, aAni
     // Hide button if zoom is at 100%
     zoomResetButton.hidden = true;
   }
 }
 
 Services.obs.addObserver(fullZoomObserver, "browser-fullZoom:zoomChange", false);
 Services.obs.addObserver(fullZoomObserver, "browser-fullZoom:zoomReset", false);
 Services.obs.addObserver(fullZoomObserver, "browser-fullZoom:location-change", false);
+Services.mm.addMessageListener("SyntheticDocument:ZoomChange", function(aMessage) {
+  updateZoomButton(aMessage.target, true, aMessage.data.value);
+});
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -95,19 +95,19 @@ add_task(function* setup() {
     Services.telemetry.setEventRecordingEnabled("navigation", false);
   });
 });
 
 add_task(function* test_simpleQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
-  resultIndexHist.clear();
+  // resultIndexHist.clear();
   resultTypeHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
@@ -125,34 +125,34 @@ add_task(function* test_simpleQuery() {
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexes = resultIndexHist.snapshot();
+  // checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
     URLBAR_SELECTED_RESULT_TYPES.searchengine,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_searchAlias() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
-  resultIndexHist.clear();
+  // resultIndexHist.clear();
   resultTypeHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Search using a search alias.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
@@ -170,34 +170,34 @@ add_task(function* test_searchAlias() {
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexes = resultIndexHist.snapshot();
+  // checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
     URLBAR_SELECTED_RESULT_TYPES.searchengine,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_oneOff() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
-  resultIndexHist.clear();
+  // resultIndexHist.clear();
   resultTypeHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
@@ -218,34 +218,34 @@ add_task(function* test_oneOff() {
   checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexes = resultIndexHist.snapshot();
+  // checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
     URLBAR_SELECTED_RESULT_TYPES.searchengine,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_suggestion() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
-  resultIndexHist.clear();
+  // resultIndexHist.clear();
   resultTypeHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
   let suggestionEngine = yield new Promise((resolve, reject) => {
@@ -278,18 +278,18 @@ add_task(function* test_suggestion() {
   checkKeyedHistogram(search_hist, searchEngineId + ".urlbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 3, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  // let resultIndexes = resultIndexHist.snapshot();
+  // checkHistogramResults(resultIndexes, 3, "FX_URLBAR_SELECTED_RESULT_INDEX");
 
   let resultTypes = resultTypeHist.snapshot();
   checkHistogramResults(resultTypes,
     URLBAR_SELECTED_RESULT_TYPES.searchsuggestion,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -104,20 +104,19 @@ toolbar:-moz-lwtheme {
   margin-top: 3px;
 }
 
 #main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
 #main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
   margin-top: var(--space-above-tabbar);
 }
 
-#navigator-toolbox {
+#navigator-toolbox,
+#navigator-toolbox > toolbar {
   -moz-appearance: none;
-  background-color: transparent;
-  border-top: none;
 }
 
 #navigator-toolbox::after {
   content: "";
   display: -moz-box;
   -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
   border-bottom: 1px solid ThreeDShadow;
 }
@@ -136,21 +135,16 @@ toolbar:-moz-lwtheme {
     }
   }
 }
 
 #navigator-toolbox:-moz-lwtheme::after {
   border-bottom-color: rgba(0,0,0,.3);
 }
 
-#navigator-toolbox > toolbar {
-  -moz-appearance: none;
-  border-style: none;
-}
-
 #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
   background-clip: padding-box;
   background-image: linear-gradient(@toolbarHighlight@, @toolbarHighlight@);
 }
 
 @media (-moz-os-version: windows-win7) {
   #nav-bar {
     background-image: linear-gradient(@toolbarHighlight@, transparent) !important;
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -46,11 +46,9 @@ ac_add_options --target=x86_64-apple-dar
 ac_add_options --with-macos-private-frameworks=$CROSS_PRIVATE_FRAMEWORKS
 
 if [ "x$MOZ_PKG_SPECIAL" != "xasan" ]; then
   # Enable static analysis checks by default on OSX cross builds.
   # Exception is ASan, where this breaks.
   ac_add_options --enable-clang-plugin
 fi
 
-. "$topsrcdir/build/mozconfig.cache"
-
 export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
--- a/build/mozconfig.artifact
+++ b/build/mozconfig.artifact
@@ -6,8 +6,9 @@ ac_add_options --enable-artifact-builds
 # Override any toolchain defines we've inherited from other mozconfigs.
 unset CC
 unset CXX
 unset HOST_CC
 unset HOST_CXX
 unset RUSTC
 unset CARGO
 unset MAKECAB
+unset TOOLCHAIN_PREFIX
--- a/build/mozconfig.cache
+++ b/build/mozconfig.cache
@@ -103,20 +103,16 @@ fi
 
 if test -z "$bucket"; then
     case "$platform" in
     win*) : ;;
     *)
         ac_add_options --with-ccache
     esac
 else
-    if ! test -e $topsrcdir/sccache2/sccache${suffix}; then
-        echo "sccache2 missing in the tooltool manifest" >&2
-        exit 1
-    fi
     mk_add_options "export SCCACHE_BUCKET=$bucket"
     case "$master" in
     *us[ew][12].mozilla.com*|*euc1.mozilla.com*)
         mk_add_options "export SCCACHE_NAMESERVER=169.254.169.253"
         ;;
     esac
     ac_add_options "--with-ccache=$topsrcdir/sccache2/sccache${suffix}"
     export SCCACHE_VERBOSE_STATS=1
--- a/build/mozconfig.clang-cl
+++ b/build/mozconfig.clang-cl
@@ -1,7 +1,9 @@
-CLANG_DIR=`cd "$topsrcdir/clang/bin" ; pwd`
-export PATH="${CLANG_DIR}:${PATH}"
+if test -d "$topsrcdir/clang/bin"; then
+    CLANG_DIR=`cd "$topsrcdir/clang/bin" ; pwd`
+    export PATH="${CLANG_DIR}:${PATH}"
 
-mk_export_correct_style PATH
+    mk_export_correct_style PATH
+fi
 
 export CC=clang-cl
 export CXX=clang-cl
--- a/build/win32/mozconfig.vs2015-win64
+++ b/build/win32/mozconfig.vs2015-win64
@@ -1,24 +1,26 @@
 if [ -z "${VSPATH}" ]; then
     TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
     VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3"
 fi
 
-VSWINPATH="$(cd ${VSPATH} && pwd -W)"
+if [ -d "${VSPATH}" ]; then
+    VSWINPATH="$(cd ${VSPATH} && pwd -W)"
 
-export WINDOWSSDKDIR="${VSWINPATH}/SDK"
-export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT"
-export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
+    export WINDOWSSDKDIR="${VSWINPATH}/SDK"
+    export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT"
+    export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
 
-export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}"
-export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}"
+    export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}"
+    export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}"
 
-export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
-export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib"
+    export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
+    export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib"
+fi
 
 . $topsrcdir/build/mozconfig.vs-common
 
 mk_export_correct_style WINDOWSSDKDIR
 mk_export_correct_style INCLUDE
 mk_export_correct_style LIB
 mk_export_correct_style PATH
 mk_export_correct_style WIN32_REDIST_DIR
--- a/build/win64/mozconfig.vs2015
+++ b/build/win64/mozconfig.vs2015
@@ -1,23 +1,25 @@
 if [ -z "${VSPATH}" ]; then
     TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
     VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3"
 fi
 
-VSWINPATH="$(cd ${VSPATH} && pwd -W)"
+if [ -d "${VSPATH}" ]; then
+    VSWINPATH="$(cd ${VSPATH} && pwd -W)"
 
-export WINDOWSSDKDIR="${VSWINPATH}/SDK"
-export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT
-export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64"
+    export WINDOWSSDKDIR="${VSWINPATH}/SDK"
+    export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT
+    export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64"
 
-export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}"
+    export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}"
 
-export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
-export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64:${VSPATH}/DIA SDK/lib/amd64"
+    export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
+    export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64:${VSPATH}/DIA SDK/lib/amd64"
+fi
 
 . $topsrcdir/build/mozconfig.vs-common
 
 mk_export_correct_style WINDOWSSDKDIR
 mk_export_correct_style INCLUDE
 mk_export_correct_style LIB
 mk_export_correct_style PATH
 mk_export_correct_style WIN32_REDIST_DIR
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -162,17 +162,17 @@ ifndef GNU_CC
 # The final PDB for libraries and programs is created by the linker and uses
 # a different name from the single PDB file created by the compiler. See
 # bug 462740.
 #
 
 ifdef SIMPLE_PROGRAMS
 COMPILE_PDB_FLAG ?= -Fd$(basename $(@F)).pdb
 else
-COMPILE_PDB_FLAG ?= -Fdgenerated.pdb
+COMPILE_PDB_FLAG ?= -Fdgenerated.pdb -FS
 endif
 COMPILE_CFLAGS += $(COMPILE_PDB_FLAG)
 COMPILE_CXXFLAGS += $(COMPILE_PDB_FLAG)
 
 LINK_PDBFILE ?= $(basename $(@F)).pdb
 ifdef MOZ_DEBUG
 CODFILE=$(basename $(@F)).cod
 endif
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -188,22 +188,17 @@ DevTools.prototype = {
   getToolDefinition: function DT_getToolDefinition(toolId) {
     let tool = this._tools.get(toolId);
     if (!tool) {
       return null;
     } else if (!tool.visibilityswitch) {
       return tool;
     }
 
-    let enabled;
-    try {
-      enabled = Services.prefs.getBoolPref(tool.visibilityswitch);
-    } catch (e) {
-      enabled = true;
-    }
+    let enabled = Services.prefs.getBoolPref(tool.visibilityswitch, true);
 
     return enabled ? tool : null;
   },
 
   /**
    * Allow ToolBoxes to get at the list of tools that they should populate
    * themselves with.
    *
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -95,27 +95,23 @@ function openToolbox({ form, chrome, isT
   let options = {
     form: form,
     client: gClient,
     chrome: chrome,
     isTabActor: isTabActor
   };
   TargetFactory.forRemoteTab(options).then(target => {
     let frame = document.getElementById("toolbox-iframe");
-    let selectedTool = "jsdebugger";
 
-    try {
-      // Remember the last panel that was used inside of this profile.
-      selectedTool = Services.prefs.getCharPref("devtools.toolbox.selectedTool");
-    } catch(e) {}
-
-    try {
-      // But if we are testing, then it should always open the debugger panel.
-      selectedTool = Services.prefs.getCharPref("devtools.browsertoolbox.panel");
-    } catch(e) {}
+    // Remember the last panel that was used inside of this profile.
+    // But if we are testing, then it should always open the debugger panel.
+    let selectedTool =
+      Services.prefs.getCharPref("devtools.browsertoolbox.panel",
+                                 Services.prefs.getCharPref("devtools.toolbox.selectedTool",
+                                                            "jsdebugger"));
 
     let options = { customIframe: frame };
     gDevTools.showToolbox(target,
                           selectedTool,
                           Toolbox.HostType.CUSTOM,
                           options)
              .then(onNewToolbox);
   });
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1221,22 +1221,17 @@ Toolbox.prototype = {
    * Ensure the visibility of each toolbox button matches the preference value.
    */
   _commandIsVisible: function (button) {
     const {
       isTargetSupported,
       visibilityswitch
     } = button;
 
-    let visible = true;
-    try {
-      visible = Services.prefs.getBoolPref(visibilityswitch);
-    } catch (ex) {
-      // Do nothing.
-    }
+    let visible = Services.prefs.getBoolPref(visibilityswitch, true);
 
     if (isTargetSupported) {
       return visible && isTargetSupported(this.target);
     }
 
     return visible;
   },
 
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -66,21 +66,18 @@ const ATTR_COLLAPSE_LENGTH_PREF = "devto
 function MarkupView(inspector, frame, controllerWindow) {
   this.inspector = inspector;
   this.walker = this.inspector.walker;
   this._frame = frame;
   this.win = this._frame.contentWindow;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
 
-  try {
-    this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
-  } catch (ex) {
-    this.maxChildren = DEFAULT_MAX_CHILDREN;
-  }
+  this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize",
+                                               DEFAULT_MAX_CHILDREN);
 
   this.collapseAttributes =
     Services.prefs.getBoolPref(ATTR_COLLAPSE_ENABLED_PREF);
   this.collapseAttributeLength =
     Services.prefs.getIntPref(ATTR_COLLAPSE_LENGTH_PREF);
 
   // Creating the popup to be used to show CSS suggestions.
   // The popup will be attached to the toolbox document.
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -790,22 +790,25 @@ let SourceActor = ActorClassWithSpec(sou
             break;
           }
         }
 
         // The above loop should never complete. We only did breakpoint sliding
         // because we found scripts on the line we started from,
         // which means there must be valid entry points somewhere
         // within those scripts.
-        assert(
-          actualLine <= maxLine,
-          "Could not find any entry points to set a breakpoint on, " +
-          "even though I was told a script existed on the line I started " +
-          "the search with."
-        );
+        if (actualLine > maxLine) {
+          return promise.reject({
+            error: "noCodeAtLineColumn",
+            message:
+              "Could not find any entry points to set a breakpoint on, " +
+              "even though I was told a script existed on the line I started " +
+              "the search with."
+          });
+        }
 
         // Update the actor to use the new location (reusing a
         // previous breakpoint if it already exists on that line).
         const actualLocation = new OriginalLocation(originalSourceActor, actualLine);
         const existingActor = this.breakpointActorMap.getActor(actualLocation);
         this.breakpointActorMap.deleteActor(originalLocation);
         if (existingActor) {
           actor.delete();
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-22.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1333219 - make that setBreakpoint fails when script is not found
+ * at the specified line.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+  run_test_with_server(DebuggerServer, function () {
+    run_test_with_server(WorkerDebuggerServer, do_test_finished);
+  });
+  do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+  gCallback = aCallback;
+  initTestDebuggerServer(aServer);
+  gDebuggee = addTestGlobal("test-breakpoints", aServer);
+  gClient = new DebuggerClient(aServer.connectPipe());
+  gClient.connect().then(function () {
+    attachTestTabAndResume(gClient,
+                           "test-breakpoints",
+                           function (aResponse, aTabClient, aThreadClient) {
+                             gThreadClient = aThreadClient;
+                             test();
+                           });
+  });
+}
+
+const test = Task.async(function* () {
+  // Populate the `ScriptStore` so that we only test that the script
+  // is added through `onNewScript`
+  yield getSources(gThreadClient);
+
+  let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient);
+  let source = gThreadClient.source(packet.frame.where.source);
+  let location = {
+    line: gDebuggee.line0 + 2
+  };
+
+  let [res, bpClient] = yield setBreakpoint(source, location);
+  ok(!res.error);
+
+  let location2 = {
+    line: gDebuggee.line0 + 5
+  };
+
+  yield source.setBreakpoint(location2).then(_ => {
+    do_throw("no code shall not be found the specified line or below it");
+  }, reason => {
+    do_check_eq(reason.error, "noCodeAtLineColumn");
+    ok(reason.message);
+  });
+
+  yield resume(gThreadClient);
+  finishClient(gClient);
+});
+
+function evalCode() {
+  // Start a new script
+  Components.utils.evalInSandbox(`
+var line0 = Error().lineNumber;
+function some_function() {
+  // breakpoint is valid here -- it slides one line below (line0 + 2)
+}
+debugger;
+// no breakpoint is allowed here (line0 + 5)
+`, gDebuggee);
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -125,16 +125,17 @@ skip-if = coverage # bug 1336670
 [test_breakpoint-16.js]
 [test_breakpoint-17.js]
 [test_breakpoint-18.js]
 [test_breakpoint-19.js]
 skip-if = true
 reason = bug 1104838
 [test_breakpoint-20.js]
 [test_breakpoint-21.js]
+[test_breakpoint-22.js]
 [test_conditional_breakpoint-01.js]
 [test_conditional_breakpoint-02.js]
 [test_conditional_breakpoint-03.js]
 [test_eventlooplag_actor.js]
 skip-if = true
 reason = only ran on B2G
 [test_listsources-01.js]
 [test_listsources-02.js]
--- a/devtools/shared/touch/simulator-core.js
+++ b/devtools/shared/touch/simulator-core.js
@@ -16,29 +16,18 @@ var systemAppOrigin = (function () {
       Services.io.newURI(Services.prefs.getCharPref("b2g.system_manifest_url"))
                  .prePath;
   } catch (e) {
     // Fall back to default value
   }
   return systemOrigin;
 })();
 
-var threshold = 25;
-try {
-  threshold = Services.prefs.getIntPref("ui.dragThresholdX");
-} catch (e) {
-  // Fall back to default value
-}
-
-var delay = 500;
-try {
-  delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
-} catch (e) {
-  // Fall back to default value
-}
+var threshold = Services.prefs.getIntPref("ui.dragThresholdX", 25);
+var delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay", 500);
 
 function SimulatorCore(simulatorTarget) {
   this.simulatorTarget = simulatorTarget;
 }
 
 /**
  * Simulate touch events for platforms where they aren't generally available.
  */
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10380,30 +10380,33 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       nsCOMPtr<nsIDocument> doc = GetDocument();
       NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
       doc->SetDocumentURI(aURI);
 
       /* This is a anchor traversal with in the same page.
        * call OnNewURI() so that, this traversal will be
        * recorded in session and global history.
        */
-      nsCOMPtr<nsIPrincipal> triggeringPrincipal, principalToInherit;
+      nsCOMPtr<nsIPrincipal> newURITriggeringPrincipal, newURIPrincipalToInherit;
       if (mOSHE) {
-        mOSHE->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
-        mOSHE->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
+        mOSHE->GetTriggeringPrincipal(getter_AddRefs(newURITriggeringPrincipal));
+        mOSHE->GetPrincipalToInherit(getter_AddRefs(newURIPrincipalToInherit));
+      } else {
+        newURITriggeringPrincipal = aTriggeringPrincipal;
+        newURIPrincipalToInherit = doc->NodePrincipal();
       }
       // Pass true for aCloneSHChildren, since we're not
       // changing documents here, so all of our subframes are
       // still relevant to the new session history entry.
       //
       // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT
       // flag on firing onLocationChange(...).
       // Anyway, aCloneSHChildren param is simply reflecting
       // doShortCircuitedLoad in this scope.
-      OnNewURI(aURI, nullptr, triggeringPrincipal, principalToInherit,
+      OnNewURI(aURI, nullptr, newURITriggeringPrincipal, newURIPrincipalToInherit,
                mLoadType, true, true, true);
 
       nsCOMPtr<nsIInputStream> postData;
       nsCOMPtr<nsISupports> cacheKey;
 
       bool scrollRestorationIsManual = false;
       if (mOSHE) {
         /* save current position of scroller(s) (bug 59774) */
new file mode 100644
--- /dev/null
+++ b/docshell/test/dummy_page.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    just a dummy html file
+  </body>
+</html>
--- a/docshell/test/mochitest.ini
+++ b/docshell/test/mochitest.ini
@@ -6,16 +6,17 @@ support-files =
   bug404548-subframe.html
   bug413310-post.sjs
   bug413310-subframe.html
   bug529119-window.html
   bug570341_recordevents.html
   bug668513_redirect.html
   bug668513_redirect.html^headers^
   bug691547_frame.html
+  dummy_page.html
   file_anchor_scroll_after_document_open.html
   file_bug385434_1.html
   file_bug385434_2.html
   file_bug385434_3.html
   file_bug475636.sjs
   file_bug509055.html
   file_bug540462.html
   file_bug580069_1.html
@@ -87,8 +88,9 @@ support-files = file_bug668513.html
 [test_bug1121701.html]
 [test_bug1186774.html]
 [test_forceinheritprincipal_overrule_owner.html]
 [test_framedhistoryframes.html]
 skip-if = toolkit == 'android' # bug 784321
 support-files = file_framedhistoryframes.html
 [test_pushState_after_document_open.html]
 [test_windowedhistoryframes.html]
+[test_triggeringprincipal_location_seturi.html]
new file mode 100644
--- /dev/null
+++ b/docshell/test/test_triggeringprincipal_location_seturi.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const SAME_ORIGIN_URI = "http://mochi.test:8888/tests/docshell/test/dummy_page.html";
+const CROSS_ORIGIN_URI = "http://example.com/tests/docshell/test/dummy_page.html";
+const NUMBER_OF_TESTS = 3;
+let testCounter = 0;
+
+function checkFinish() {
+  testCounter++;
+  if (testCounter < NUMBER_OF_TESTS) {
+  	return;
+  }
+  SimpleTest.finish();
+}
+
+// ---- test 1 ----
+
+let myFrame1 = document.createElement("iframe");
+myFrame1.src = SAME_ORIGIN_URI;
+myFrame1.addEventListener("load", checkLoadFrame1);
+document.documentElement.appendChild(myFrame1);
+
+function checkLoadFrame1() {
+  myFrame1.removeEventListener('load', checkLoadFrame1, false);
+  // window.location.href is no longer cross-origin accessible in gecko.
+  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI,
+    "initial same origin dummy loaded into frame1");
+
+  SpecialPowers.wrap(myFrame1.contentWindow).location.hash = "#bar";
+  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar",
+    "initial same origin dummy#bar loaded into iframe1");
+
+  myFrame1.addEventListener("load", checkNavFrame1);
+  myFrame1.src = CROSS_ORIGIN_URI;
+}
+
+function checkNavFrame1() {
+  myFrame1.removeEventListener('load', checkNavFrame1, false);
+  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, CROSS_ORIGIN_URI,
+    "cross origin dummy loaded into frame1");
+
+  myFrame1.addEventListener("load", checkBackNavFrame1);
+  myFrame1.src = SAME_ORIGIN_URI + "#bar";
+}
+
+function checkBackNavFrame1() {
+  myFrame1.removeEventListener('load', checkBackNavFrame1, false);
+  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar",
+    "navagiating back to same origin dummy for frame1");
+  checkFinish();
+}
+
+// ---- test 2 ----
+
+let myFrame2 = document.createElement("iframe");
+myFrame2.src = "about:blank";
+myFrame2.addEventListener("load", checkLoadFrame2);
+document.documentElement.appendChild(myFrame2);
+
+function checkLoadFrame2() {
+  myFrame2.removeEventListener('load', checkLoadFrame2, false);
+  is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank",
+    "initial about:blank frame loaded");
+
+  myFrame2.contentWindow.location.hash = "#foo";
+  is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank#foo",
+      "about:blank#foo frame loaded");
+  
+  myFrame2.addEventListener('load', checkHistoryFrame2);
+  myFrame2.src = "about:blank";
+}
+
+function checkHistoryFrame2() {
+  myFrame2.removeEventListener('load', checkHistoryFrame2, false);
+  is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank",
+    "about:blank frame loaded again");
+  checkFinish();
+}
+
+// ---- test 3 ----
+
+let myFrame3 = document.createElement("frame");
+document.documentElement.appendChild(myFrame3);
+myFrame3.contentWindow.location.hash = "#foo";
+
+is(myFrame3.contentWindow.location.href, "about:blank#foo",
+   "created history entry with about:blank#foo");
+checkFinish();
+
+</script>
+</body>
+</html>
--- a/dom/animation/ComputedTimingFunction.cpp
+++ b/dom/animation/ComputedTimingFunction.cpp
@@ -13,17 +13,17 @@ namespace mozilla {
 void
 ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
 {
   mType = aFunction.mType;
   if (nsTimingFunction::IsSplineType(mType)) {
     mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
                          aFunction.mFunc.mX2, aFunction.mFunc.mY2);
   } else {
-    mSteps = aFunction.mSteps;
+    mStepsOrFrames = aFunction.mStepsOrFrames;
   }
 }
 
 static inline double
 StepTiming(uint32_t aSteps,
            double aPortion,
            ComputedTimingFunction::BeforeFlag aBeforeFlag,
            nsTimingFunction::Type aType)
@@ -53,17 +53,32 @@ StepTiming(uint32_t aSteps,
   // input outside that range. This takes care of steps that would otherwise
   // occur at boundaries.
   if (result < 0.0 && aPortion >= 0.0) {
     return 0.0;
   }
   if (result > 1.0 && aPortion <= 1.0) {
     return 1.0;
   }
+  return result;
+}
 
+static inline double
+FramesTiming(uint32_t aFrames, double aPortion)
+{
+  MOZ_ASSERT(aFrames > 1, "the number of frames must be greater than 1");
+  int32_t currentFrame = floor(aPortion * aFrames);
+  double result = double(currentFrame) / double(aFrames - 1);
+
+  // Don't overshoot the natural range of the animation (by producing an output
+  // progress greater than 1.0) when we are at the exact end of its interval
+  // (i.e. the input progress is 1.0).
+  if (result > 1.0 && aPortion <= 1.0) {
+    return 1.0;
+  }
   return result;
 }
 
 double
 ComputedTimingFunction::GetValue(
     double aPortion,
     ComputedTimingFunction::BeforeFlag aBeforeFlag) const
 {
@@ -108,35 +123,38 @@ ComputedTimingFunction::GetValue(
       }
       // If we can't calculate a sensible tangent, don't extrapolate at all.
       return 1.0;
     }
 
     return mTimingFunction.GetSplineValue(aPortion);
   }
 
-  return StepTiming(mSteps, aPortion, aBeforeFlag, mType);
+  return mType == nsTimingFunction::Type::Frames
+         ? FramesTiming(mStepsOrFrames, aPortion)
+         : StepTiming(mStepsOrFrames, aPortion, aBeforeFlag, mType);
 }
 
 int32_t
 ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
 {
   if (mType != aRhs.mType) {
     return int32_t(mType) - int32_t(aRhs.mType);
   }
 
   if (mType == nsTimingFunction::Type::CubicBezier) {
     int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
     if (order != 0) {
       return order;
     }
   } else if (mType == nsTimingFunction::Type::StepStart ||
-             mType == nsTimingFunction::Type::StepEnd) {
-    if (mSteps != aRhs.mSteps) {
-      return int32_t(mSteps) - int32_t(aRhs.mSteps);
+             mType == nsTimingFunction::Type::StepEnd ||
+             mType == nsTimingFunction::Type::Frames) {
+    if (mStepsOrFrames != aRhs.mStepsOrFrames) {
+      return int32_t(mStepsOrFrames) - int32_t(aRhs.mStepsOrFrames);
     }
   }
 
   return 0;
 }
 
 void
 ComputedTimingFunction::AppendToString(nsAString& aResult) const
@@ -146,17 +164,20 @@ ComputedTimingFunction::AppendToString(n
       nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
                                                    mTimingFunction.Y1(),
                                                    mTimingFunction.X2(),
                                                    mTimingFunction.Y2(),
                                                    aResult);
       break;
     case nsTimingFunction::Type::StepStart:
     case nsTimingFunction::Type::StepEnd:
-      nsStyleUtil::AppendStepsTimingFunction(mType, mSteps, aResult);
+      nsStyleUtil::AppendStepsTimingFunction(mType, mStepsOrFrames, aResult);
+      break;
+    case nsTimingFunction::Type::Frames:
+      nsStyleUtil::AppendFramesTimingFunction(mStepsOrFrames, aResult);
       break;
     default:
       nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
       break;
   }
 }
 
 /* static */ int32_t
--- a/dom/animation/ComputedTimingFunction.h
+++ b/dom/animation/ComputedTimingFunction.h
@@ -25,23 +25,33 @@ public:
   double GetValue(double aPortion, BeforeFlag aBeforeFlag) const;
   const nsSMILKeySpline* GetFunction() const
   {
     NS_ASSERTION(HasSpline(), "Type mismatch");
     return &mTimingFunction;
   }
   nsTimingFunction::Type GetType() const { return mType; }
   bool HasSpline() const { return nsTimingFunction::IsSplineType(mType); }
-  uint32_t GetSteps() const { return mSteps; }
+  uint32_t GetSteps() const
+  {
+    MOZ_ASSERT(mType == nsTimingFunction::Type::StepStart ||
+               mType == nsTimingFunction::Type::StepEnd);
+    return mStepsOrFrames;
+  }
+  uint32_t GetFrames() const
+  {
+    MOZ_ASSERT(mType == nsTimingFunction::Type::Frames);
+    return mStepsOrFrames;
+  }
   bool operator==(const ComputedTimingFunction& aOther) const
   {
     return mType == aOther.mType &&
            (HasSpline() ?
             mTimingFunction == aOther.mTimingFunction :
-            mSteps == aOther.mSteps);
+            mStepsOrFrames == aOther.mStepsOrFrames);
   }
   bool operator!=(const ComputedTimingFunction& aOther) const
   {
     return !(*this == aOther);
   }
   int32_t Compare(const ComputedTimingFunction& aRhs) const;
   void AppendToString(nsAString& aResult) const;
 
@@ -52,14 +62,14 @@ public:
     return aFunction ? aFunction->GetValue(aPortion, aBeforeFlag) : aPortion;
   }
   static int32_t Compare(const Maybe<ComputedTimingFunction>& aLhs,
                          const Maybe<ComputedTimingFunction>& aRhs);
 
 private:
   nsTimingFunction::Type mType;
   nsSMILKeySpline mTimingFunction;
-  uint32_t mSteps;
+  uint32_t mStepsOrFrames;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ComputedTimingFunction_h
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -373,25 +373,26 @@ KeyframeEffectReadOnly::DoUpdateProperti
 
 /* static */ StyleAnimationValue
 KeyframeEffectReadOnly::CompositeValue(
   nsCSSPropertyID aProperty,
   const StyleAnimationValue& aValueToComposite,
   const StyleAnimationValue& aUnderlyingValue,
   CompositeOperation aCompositeOperation)
 {
+  // Just return the underlying value if |aValueToComposite| is null
+  // (i.e. missing keyframe).
+  if (aValueToComposite.IsNull()) {
+    return aUnderlyingValue;
+  }
+
   switch (aCompositeOperation) {
     case dom::CompositeOperation::Replace:
       return aValueToComposite;
     case dom::CompositeOperation::Add: {
-      // Just return the underlying value if |aValueToComposite| is null (i.e.
-      // missing keyframe).
-      if (aValueToComposite.IsNull()) {
-        return aUnderlyingValue;
-      }
       StyleAnimationValue result(aValueToComposite);
       return StyleAnimationValue::Add(aProperty,
                                       aUnderlyingValue,
                                       Move(result));
     }
     case dom::CompositeOperation::Accumulate: {
       StyleAnimationValue result(aValueToComposite);
       return StyleAnimationValue::Accumulate(aProperty,
@@ -458,57 +459,44 @@ StyleAnimationValue
 KeyframeEffectReadOnly::CompositeValue(
   nsCSSPropertyID aProperty,
   const RefPtr<AnimValuesStyleRule>& aAnimationRule,
   const StyleAnimationValue& aValueToComposite,
   CompositeOperation aCompositeOperation)
 {
   MOZ_ASSERT(mTarget, "CompositeValue should be called with target element");
 
-  StyleAnimationValue result = aValueToComposite;
-
-  if (aCompositeOperation == CompositeOperation::Replace) {
-    MOZ_ASSERT(!aValueToComposite.IsNull(),
-      "Input value should be valid in case of replace composite");
-    // Just copy the input value in case of 'Replace'.
-    return result;
+  // FIXME: Bug 1311257: Get the base value for the servo backend.
+  if (mDocument->IsStyledByServo()) {
+    return aValueToComposite;
   }
 
-  // FIXME: Bug 1311257: Get the base value for the servo backend.
-  if (mDocument->IsStyledByServo()) {
-    return result;
-  }
-
-  MOZ_ASSERT(!aValueToComposite.IsNull() ||
-             aCompositeOperation == CompositeOperation::Add,
-             "InputValue should be null only if additive composite");
-
-  result = GetUnderlyingStyle(aProperty, aAnimationRule);
+  StyleAnimationValue underlyingValue =
+    GetUnderlyingStyle(aProperty, aAnimationRule);
 
   return CompositeValue(aProperty,
                         aValueToComposite,
-                        result,
+                        underlyingValue,
                         aCompositeOperation);
 }
 
 void
 KeyframeEffectReadOnly::EnsureBaseStyles(
   nsStyleContext* aStyleContext,
   const nsTArray<AnimationProperty>& aProperties)
 {
   if (!mTarget) {
     return;
   }
 
   mBaseStyleValues.Clear();
 
   for (const AnimationProperty& property : aProperties) {
     for (const AnimationPropertySegment& segment : property.mSegments) {
-      if (segment.mFromComposite == dom::CompositeOperation::Replace &&
-          segment.mToComposite == dom::CompositeOperation::Replace) {
+      if (segment.HasReplacableValues()) {
         continue;
       }
 
       Unused << ResolveBaseStyle(property.mProperty, aStyleContext);
       break;
     }
   }
 }
@@ -1226,26 +1214,43 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
 
     JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
     for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
       nsAutoString stringValue;
       if (propertyValue.mServoDeclarationBlock) {
         Servo_DeclarationBlock_SerializeOneValue(
           propertyValue.mServoDeclarationBlock,
           propertyValue.mProperty, &stringValue);
+      } else if (nsCSSProps::IsShorthand(propertyValue.mProperty)) {
+         // nsCSSValue::AppendToString does not accept shorthands properties but
+         // works with token stream values if we pass eCSSProperty_UNKNOWN as
+         // the property.
+         propertyValue.mValue.AppendToString(
+           eCSSProperty_UNKNOWN, stringValue, nsCSSValue::eNormalized);
       } else {
-        // nsCSSValue::AppendToString does not accept shorthands properties but
-        // works with token stream values if we pass eCSSProperty_UNKNOWN as
-        // the property.
-        nsCSSPropertyID propertyForSerializing =
-          nsCSSProps::IsShorthand(propertyValue.mProperty)
-          ? eCSSProperty_UNKNOWN
-          : propertyValue.mProperty;
-        propertyValue.mValue.AppendToString(
-          propertyForSerializing, stringValue, nsCSSValue::eNormalized);
+        nsCSSValue cssValue = propertyValue.mValue;
+        if (cssValue.GetUnit() == eCSSUnit_Null) {
+          // We use an uninitialized nsCSSValue to represent the
+          // "neutral value". We currently only do this for keyframes generated
+          // from CSS animations with missing 0%/100% keyframes. Furthermore,
+          // currently (at least until bug 1339334) keyframes generated from
+          // CSS animations only contain longhand properties so we only need to
+          // handle null nsCSSValues for longhand properties.
+          DebugOnly<bool> uncomputeResult =
+            StyleAnimationValue::UncomputeValue(
+              propertyValue.mProperty, Move(BaseStyle(propertyValue.mProperty)),
+              cssValue);
+
+          MOZ_ASSERT(uncomputeResult,
+                     "Unable to get specified value from computed value");
+          MOZ_ASSERT(cssValue.GetUnit() != eCSSUnit_Null,
+                     "Got null computed value");
+        }
+        cssValue.AppendToString(propertyValue.mProperty,
+                                stringValue, nsCSSValue::eNormalized);
       }
 
       const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
       JS::Rooted<JS::Value> value(aCx);
       if (!ToJSValue(aCx, stringValue, &value) ||
           !JS_DefineProperty(aCx, keyframeObject, name, value,
                              JSPROP_ENUMERATE)) {
         aRv.Throw(NS_ERROR_FAILURE);
@@ -1620,22 +1625,21 @@ KeyframeEffectReadOnly::CalculateCumulat
   if (mDocument->IsStyledByServo()) {
     // FIXME (bug 1303235): Do this for Servo too
     return;
   }
   mCumulativeChangeHint = nsChangeHint(0);
 
   for (const AnimationProperty& property : mProperties) {
     for (const AnimationPropertySegment& segment : property.mSegments) {
-      // In case composite operation is not 'replace', we can't throttle
-      // animations which will not cause any layout changes on invisible
-      // elements because we can't calculate the change hint for such properties
-      // until we compose it.
-      if (segment.mFromComposite != CompositeOperation::Replace ||
-          segment.mToComposite != CompositeOperation::Replace) {
+      // In case composite operation is not 'replace' or value is null,
+      // we can't throttle animations which will not cause any layout changes
+      // on invisible elements because we can't calculate the change hint for
+      // such properties until we compose it.
+      if (!segment.HasReplacableValues()) {
         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
         return;
       }
       RefPtr<nsStyleContext> fromContext =
         CreateStyleContextForAnimationValue(property.mProperty,
                                             segment.mFromValue.mGecko,
                                             aStyleContext);
 
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -61,16 +61,33 @@ struct AnimationPropertySegment
   // NOTE: In the case that no keyframe for 0 or 1 offset is specified
   // the unit of mFromValue or mToValue is eUnit_Null.
   AnimationValue mFromValue, mToValue;
 
   Maybe<ComputedTimingFunction> mTimingFunction;
   dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
   dom::CompositeOperation mToComposite = dom::CompositeOperation::Replace;
 
+  bool HasReplacableValues() const
+  {
+    return HasReplacableFromValue() && HasReplacableToValue();
+  }
+
+  bool HasReplacableFromValue() const
+  {
+    return !mFromValue.IsNull() &&
+           mFromComposite == dom::CompositeOperation::Replace;
+  }
+
+  bool HasReplacableToValue() const
+  {
+    return !mToValue.IsNull() &&
+           mToComposite == dom::CompositeOperation::Replace;
+  }
+
   bool operator==(const AnimationPropertySegment& aOther) const
   {
     return mFromKey == aOther.mFromKey &&
            mToKey == aOther.mToKey &&
            mFromValue == aOther.mFromValue &&
            mToValue == aOther.mToValue &&
            mTimingFunction == aOther.mTimingFunction &&
            mFromComposite == aOther.mFromComposite &&
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -649,16 +649,22 @@ KeyframeUtils::GetComputedKeyframeValues
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aElement, aStyleContext,
               tokenStream->mTokenStream, /* aUseSVGMode */ false, values) ||
             IsComputeValuesFailureKey(pair)) {
           continue;
         }
+      } else if (pair.mValue.GetUnit() == eCSSUnit_Null) {
+        // An uninitialized nsCSSValue represents the underlying value which
+        // we represent as an uninitialized AnimationValue so we just leave
+        // neutralPair->mValue as-is.
+        PropertyStyleAnimationValuePair* neutralPair = values.AppendElement();
+        neutralPair->mProperty = pair.mProperty;
       } else {
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aElement, aStyleContext,
               pair.mValue, /* aUseSVGMode */ false, values)) {
           continue;
         }
         MOZ_ASSERT(values.Length() == 1,
                   "Longhand properties should produce a single"
@@ -1158,33 +1164,31 @@ IsComputeValuesFailureKey(const Property
 
 static void
 AppendInitialSegment(AnimationProperty* aAnimationProperty,
                      const KeyframeValueEntry& aFirstEntry)
 {
   AnimationPropertySegment* segment =
     aAnimationProperty->mSegments.AppendElement();
   segment->mFromKey        = 0.0f;
-  segment->mFromComposite  = dom::CompositeOperation::Add;
   segment->mToKey          = aFirstEntry.mOffset;
   segment->mToValue        = aFirstEntry.mValue;
   segment->mToComposite    = aFirstEntry.mComposite;
 }
 
 static void
 AppendFinalSegment(AnimationProperty* aAnimationProperty,
                    const KeyframeValueEntry& aLastEntry)
 {
   AnimationPropertySegment* segment =
     aAnimationProperty->mSegments.AppendElement();
   segment->mFromKey        = aLastEntry.mOffset;
   segment->mFromValue      = aLastEntry.mValue;
   segment->mFromComposite  = aLastEntry.mComposite;
   segment->mToKey          = 1.0f;
-  segment->mToComposite    = dom::CompositeOperation::Add;
   segment->mTimingFunction = aLastEntry.mTimingFunction;
 }
 
 // Returns a newly created AnimationProperty if one was created to fill-in the
 // missing keyframe, nullptr otherwise (if we decided not to fill the keyframe
 // becase we don't support additive animation).
 static AnimationProperty*
 HandleMissingInitialKeyframe(nsTArray<AnimationProperty>& aResult,
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -133,16 +133,17 @@ TimingParams::ParseEasing(const nsAStrin
         case eCSSUnit_Enumerated:
           // Return Nothing() if "linear" is passed in.
           if (list->mValue.GetIntValue() ==
               NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR) {
             return Nothing();
           }
           MOZ_FALLTHROUGH;
         case eCSSUnit_Cubic_Bezier:
+        case eCSSUnit_Function:
         case eCSSUnit_Steps: {
           nsTimingFunction timingFunction;
           nsRuleNode::ComputeTimingFunction(list->mValue, timingFunction);
           ComputedTimingFunction computedTimingFunction;
           computedTimingFunction.Init(timingFunction);
           return Some(computedTimingFunction);
         }
         default:
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -7,14 +7,15 @@ support-files =
   chrome/file_animation_performance_warning.html
 
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
+[chrome/test_cssanimation_missing_keyframes.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_observers_for_sync_api.html]
 [chrome/test_restyles.html]
 skip-if = os == 'android' && processor == 'x86' # bug 1335986
 [chrome/test_running_on_compositor.html]
 [chrome/test_simulate_compute_values_failure.html]
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -19,877 +19,851 @@
 }
 div {
   font-size: 10px; /* For calculating em-based units */
 }
 </style>
 <script>
 'use strict';
 
-function assert_properties_equal(actual, expected) {
-  assert_equals(actual.length, expected.length);
-
-  var compareProperties = (a, b) =>
-    a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
-
-  var sortedActual   = actual.sort(compareProperties);
-  var sortedExpected = expected.sort(compareProperties);
-
-  var serializeValues = values =>
-    values.map(value =>
-      '{ ' +
-        [ 'offset', 'value', 'easing', 'composite' ].map(
-          member => `${member}: ${value[member]}`
-        ).join(', ') +
-      ' }')
-    .join(', ');
-
-  for (var i = 0; i < sortedActual.length; i++) {
-    assert_equals(sortedActual[i].property,
-                  sortedExpected[i].property,
-                  'CSS property name should match');
-    assert_equals(serializeValues(sortedActual[i].values),
-                  serializeValues(sortedExpected[i].values),
-                  `Values arrays do not match for `
-                  + `${sortedActual[i].property} property`);
-  }
-}
-
-// Shorthand for constructing a value object
-function value(offset, value, composite, easing) {
-  return { offset: offset, value: value, easing: easing, composite: composite };
-}
-
 var gTests = [
 
   // ---------------------------------------------------------------------
   //
   // Tests for property-indexed specifications
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a one-property two-value property-indexed specification',
     frames:   { left: ['10px', '20px'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] } ]
   },
   { desc:     'a one-shorthand-property two-value property-indexed'
               + ' specification',
     frames:   { margin: ['10px', '10px 20px 30px 40px'] },
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '10px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] } ]
   },
   { desc:     'a two-property (one shorthand and one of its longhand'
               + ' components) two-value property-indexed specification',
     frames:   { marginTop: ['50px', '60px'],
                 margin: ['10px', '10px 20px 30px 40px'] },
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '50px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] },
+                  values: [ valueFormat(0, '50px', 'replace', 'linear'),
+                            valueFormat(1, '60px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] } ]
   },
   { desc:     'a two-property property-indexed specification with different'
               + ' numbers of values',
     frames:   { left: ['10px', '20px', '30px'],
                 top: ['40px', '50px'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a property-indexed specification with an invalid value',
     frames:   { left: ['10px', '20px', '30px', '40px', '50px'],
                 top:  ['15px', '25px', 'invalid', '45px', '55px'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '20px', 'replace', 'linear'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(0.75, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '15px', 'replace', 'linear'),
-                            value(0.25, '25px', 'replace', 'linear'),
-                            value(0.75, '45px', 'replace', 'linear'),
-                            value(1, '55px', 'replace') ] } ]
+                  values: [ valueFormat(0, '15px', 'replace', 'linear'),
+                            valueFormat(0.25, '25px', 'replace', 'linear'),
+                            valueFormat(0.75, '45px', 'replace', 'linear'),
+                            valueFormat(1, '55px', 'replace') ] } ]
   },
   { desc:     'a one-property two-value property-indexed specification that'
               + ' needs to stringify its values',
     frames:   { opacity: [0, 1] },
     expected: [ { property: 'opacity',
-                  values: [ value(0, '0', 'replace', 'linear'),
-                            value(1, '1', 'replace') ] } ]
+                  values: [ valueFormat(0, '0', 'replace', 'linear'),
+                            valueFormat(1, '1', 'replace') ] } ]
   },
   { desc:     'a property-indexed keyframe where a lesser shorthand precedes'
               + ' a greater shorthand',
     frames:   { borderLeft: [ '1px solid rgb(1, 2, 3)',
                               '2px solid rgb(4, 5, 6)' ],
                 border:     [ '3px dotted rgb(7, 8, 9)',
                               '4px dashed rgb(10, 11, 12)' ] },
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(4, 5, 6)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '2px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '2px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'solid', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'solid', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
   { desc:     'a property-indexed keyframe where a greater shorthand precedes'
               + ' a lesser shorthand',
     frames:   { border:     [ '3px dotted rgb(7, 8, 9)',
                               '4px dashed rgb(10, 11, 12)' ],
                 borderLeft: [ '1px solid rgb(1, 2, 3)',
                               '2px solid rgb(4, 5, 6)' ] },
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(4, 5, 6)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '2px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '2px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'solid', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'solid', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for keyframe sequences
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a keyframe sequence specification with repeated values at'
               + ' offset 0/1 with different easings',
     frames:   [ { offset: 0.0, left: '100px', easing: 'ease' },
                 { offset: 0.0, left: '200px', easing: 'ease' },
                 { offset: 0.5, left: '300px', easing: 'linear' },
                 { offset: 1.0, left: '400px', easing: 'ease-out' },
                 { offset: 1.0, left: '500px', easing: 'step-end' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '100px', 'replace'),
-                            value(0, '200px', 'replace', 'ease'),
-                            value(0.5, '300px', 'replace', 'linear'),
-                            value(1, '400px', 'replace'),
-                            value(1, '500px', 'replace') ] } ]
+                  values: [ valueFormat(0, '100px', 'replace'),
+                            valueFormat(0, '200px', 'replace', 'ease'),
+                            valueFormat(0.5, '300px', 'replace', 'linear'),
+                            valueFormat(1, '400px', 'replace'),
+                            valueFormat(1, '500px', 'replace') ] } ]
   },
   { desc:     'a one-property two-keyframe sequence',
     frames:   [ { offset: 0, left: '10px' },
                 { offset: 1, left: '20px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] } ]
   },
   { desc:     'a two-property two-keyframe sequence',
     frames:   [ { offset: 0, left: '10px', top: '30px' },
                 { offset: 1, left: '20px', top: '40px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '30px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
+                  values: [ valueFormat(0, '30px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] } ]
   },
   { desc:     'a one shorthand property two-keyframe sequence',
     frames:   [ { offset: 0, margin: '10px' },
                 { offset: 1, margin: '20px 30px 40px 50px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a two-property (a shorthand and one of its component longhands)'
               + ' two-keyframe sequence',
     frames:   [ { offset: 0, margin: '10px', marginTop: '20px' },
                 { offset: 1, marginTop: '70px',
                              margin: '30px 40px 50px 60px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '70px', 'replace') ] },
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '70px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '60px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence with duplicate values for a given interior'
               + ' offset',
     frames:   [ { offset: 0.0, left: '10px' },
                 { offset: 0.5, left: '20px' },
                 { offset: 0.5, left: '30px' },
                 { offset: 0.5, left: '40px' },
                 { offset: 1.0, left: '50px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.5, '20px', 'replace'),
+                            valueFormat(0.5, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence with duplicate values for offsets 0 and 1',
     frames:   [ { offset: 0, left: '10px' },
                 { offset: 0, left: '20px' },
                 { offset: 0, left: '30px' },
                 { offset: 1, left: '40px' },
                 { offset: 1, left: '50px' },
                 { offset: 1, left: '60px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace'),
-                            value(0, '30px', 'replace', 'linear'),
-                            value(1, '40px', 'replace'),
-                            value(1, '60px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace'),
+                            valueFormat(0, '30px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace'),
+                            valueFormat(1, '60px', 'replace') ] } ]
   },
   { desc:     'a two-property four-keyframe sequence',
     frames:   [ { offset: 0, left: '10px' },
                 { offset: 0, top: '20px' },
                 { offset: 1, top: '30px' },
                 { offset: 1, left: '40px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] } ]
   },
   { desc:     'a one-property keyframe sequence with some omitted offsets',
     frames:   [ { offset: 0.00, left: '10px' },
                 { offset: 0.25, left: '20px' },
                 { left: '30px' },
                 { left: '40px' },
                 { offset: 1.00, left: '50px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '20px', 'replace', 'linear'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(0.75, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a two-property keyframe sequence with some omitted offsets',
     frames:   [ { offset: 0.00, left: '10px', top: '20px' },
                 { offset: 0.25, left: '30px' },
                 { left: '40px' },
                 { left: '50px', top: '60px' },
                 { offset: 1.00, left: '70px', top: '80px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '30px', 'replace', 'linear'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(0.75, '50px', 'replace', 'linear'),
-                            value(1, '70px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '30px', 'replace', 'linear'),
+                            valueFormat(0.5, '40px', 'replace', 'linear'),
+                            valueFormat(0.75, '50px', 'replace', 'linear'),
+                            valueFormat(1, '70px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(0.75, '60px', 'replace', 'linear'),
-                            value(1, '80px', 'replace') ] } ]
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(0.75, '60px', 'replace', 'linear'),
+                            valueFormat(1, '80px', 'replace') ] } ]
   },
   { desc:     'a one-property keyframe sequence with all omitted offsets',
     frames:   [ { left: '10px' },
                 { left: '20px' },
                 { left: '30px' },
                 { left: '40px' },
                 { left: '50px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '20px', 'replace', 'linear'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(0.75, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence with different easing values, but the'
               + ' same easing value for a given offset',
     frames:   [ { offset: 0.0, easing: 'ease',     left: '10px'},
                 { offset: 0.0, easing: 'ease',     top: '20px'},
                 { offset: 0.5, easing: 'linear',   left: '30px' },
                 { offset: 0.5, easing: 'linear',   top: '40px' },
                 { offset: 1.0, easing: 'step-end', left: '50px' },
                 { offset: 1.0, easing: 'step-end', top: '60px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'ease'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'ease'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'ease'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] } ]
+                  values: [ valueFormat(0, '20px', 'replace', 'ease'),
+                            valueFormat(0.5, '40px', 'replace', 'linear'),
+                            valueFormat(1, '60px', 'replace') ] } ]
   },
   { desc:     'a one-property two-keyframe sequence that needs to'
               + ' stringify its values',
     frames:   [ { offset: 0, opacity: 0 },
                 { offset: 1, opacity: 1 } ],
     expected: [ { property: 'opacity',
-                  values: [ value(0, '0', 'replace', 'linear'),
-                            value(1, '1', 'replace') ] } ]
+                  values: [ valueFormat(0, '0', 'replace', 'linear'),
+                            valueFormat(1, '1', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where shorthand precedes longhand',
     frames:   [ { offset: 0, margin: '10px', marginRight: '20px' },
                 { offset: 1, margin: '30px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where longhand precedes shorthand',
     frames:   [ { offset: 0, marginRight: '20px', margin: '10px' },
                 { offset: 1, margin: '30px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where lesser shorthand precedes greater'
               + ' shorthand',
     frames:   [ { offset: 0, borderLeft: '1px solid rgb(1, 2, 3)',
                              border: '2px dotted rgb(4, 5, 6)' },
                 { offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where greater shorthand precedes'
               + ' lesser shorthand',
     frames:   [ { offset: 0, border: '2px dotted rgb(4, 5, 6)',
                              borderLeft: '1px solid rgb(1, 2, 3)' },
                 { offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for unit conversion
   //
   // ---------------------------------------------------------------------
 
   { desc:     'em units are resolved to px values',
     frames:   { left: ['10em', '20em'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '100px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] } ]
+                  values: [ valueFormat(0, '100px', 'replace', 'linear'),
+                            valueFormat(1, '200px', 'replace') ] } ]
   },
   { desc:     'calc() expressions are resolved to the equivalent units',
     frames:   { left: ['calc(10em + 10px)', 'calc(10em + 10%)'] },
     expected: [ { property: 'left',
-                  values: [ value(0, 'calc(110px)', 'replace', 'linear'),
-                            value(1, 'calc(100px + 10%)', 'replace') ] } ]
+                  values: [ valueFormat(0, 'calc(110px)', 'replace', 'linear'),
+                            valueFormat(1, 'calc(100px + 10%)', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for CSS variable handling conversion
   //
   // ---------------------------------------------------------------------
 
   { desc:     'CSS variables are resolved to their corresponding values',
     frames:   { left: ['10px', 'var(--var-100px)'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '100px', 'replace') ] } ]
   },
   { desc:     'CSS variables in calc() expressions are resolved',
     frames:   { left: ['10px', 'calc(var(--var-100px) / 2 - 10%)'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, 'calc(50px + -10%)', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, 'calc(50px + -10%)', 'replace') ] } ]
   },
   { desc:     'CSS variables in shorthands are resolved to their corresponding'
               + ' values',
     frames:   { margin: ['10px', 'var(--var-100px-200px)'] },
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '100px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '200px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '100px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '200px', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for properties that parse correctly but which we fail to
   // convert to computed values.
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a missing property in initial keyframe',
     frames:   [ { },
                 { margin: '5px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] } ]
   },
   { desc:     'a missing property in initial keyframe and there are some ' +
               'keyframes with the same offset',
     frames:   [ { },
                 { margin: '10px', offset: 0.5 },
                 { margin: '20px', offset: 0.5 },
                 { margin: '30px'} ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe',
     frames:   [ { margin: '5px' },
                 { } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe and there are some ' +
               'keyframes with the same offsets',
     frames:   [ { margin: '5px' },
                 { margin: '10px', offset: 0.5 },
                 { margin: '20px', offset: 0.5 },
                 { } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0,   '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   undefined, 'add') ] },
+                  values: [ valueFormat(0,   '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1,   undefined, 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe where it forms the last'
               + ' segment in the series',
     frames:   [ { margin: '5px' },
                 { marginLeft: '5px',
                   marginRight: '5px',
                   marginBottom: '5px' } ],
     expected: [ { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'a missing property in initial keyframe along with other values',
     frames:   [ {                left: '10px' },
                 { margin: '5px', left: '20px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-top',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe along with other values',
     frames:   [ { margin: '5px', left: '10px' },
                 {                left: '20px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'missing properties in both of initial and final keyframe',
     frames:   [ { left: '5px', offset: 0.5 } ],
     expected: [ { property: 'left',
-                  values: [ value(0,   undefined, 'add',     'linear'),
-                            value(0.5, '5px',       'replace', 'linear'),
-                            value(1,   undefined, 'add') ] } ]
+                  values: [ valueFormat(0,   undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '5px',     'replace', 'linear'),
+                            valueFormat(1,   undefined, 'replace') ] } ]
   },
   { desc:     'missing propertes in both of initial and final keyframe along '
               + 'with other values',
     frames:   [ { left:  '5px',  offset: 0 },
                 { right: '5px',  offset: 0.5 },
                 { left:  '10px', offset: 1 } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '5px',  'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
+                  values: [ valueFormat(0, '5px',  'replace', 'linear'),
+                            valueFormat(1, '10px', 'replace') ] },
                 { property: 'right',
-                  values: [ value(0,   undefined, 'add',     'linear'),
-                            value(0.5, '5px',     'replace', 'linear'),
-                            value(1,   undefined, 'add') ] } ]
+                  values: [ valueFormat(0,   undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '5px',     'replace', 'linear'),
+                            valueFormat(1,   undefined, 'replace') ] } ]
   },
 
   { desc:     'a missing property in final keyframe with duplicate offset ' +
               + 'along with other values',
     frames:   [ { left: '5px',  right: '5px', offset: 0 },
                 { left: '8px',  right: '8px', offset: 0 },
                 { left: '10px',               offset: 1 } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '5px',  'replace'),
-                            value(0, '8px',  'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
+                  values: [ valueFormat(0, '5px',  'replace'),
+                            valueFormat(0, '8px',  'replace', 'linear'),
+                            valueFormat(1, '10px', 'replace') ] },
                 { property: 'right',
-                  values: [ value(0, '5px',     'replace'),
-                            value(0, '8px',     'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px',     'replace'),
+                            valueFormat(0, '8px',     'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
 
   { desc:     'a missing property in initial keyframe with duplicate offset '
               + 'along with other values',
     frames:   [ { left: '10px',              offset: 0 },
                 { left: '8px', right: '8px', offset: 1 },
                 { left: '5px', right: '5px', offset: 1 } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '8px',  'replace'),
-                            value(1, '5px',  'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '8px',  'replace'),
+                            valueFormat(1, '5px',  'replace') ] },
                 { property: 'right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '8px',     'replace'),
-                            value(1, '5px',     'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '8px',     'replace'),
+                            valueFormat(1, '5px',     'replace') ] } ]
   },
 ];
 
 SpecialPowers.pushPrefEnv(
   { set: [["dom.animations-api.core.enabled", true]] },
   function() {
     gTests.forEach(function(subtest) {
       test(function(t) {
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_cssanimation_missing_keyframes.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Bug 1339332 - Test for missing keyframes in CSS Animation</title>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1339332"
+   target="_blank">Mozilla Bug 1339332</a>
+<div id="log"></div>
+<style>
+@keyframes missingFrom {
+  to {
+    text-align: right;
+  }
+}
+@keyframes missingBoth {
+  50% {
+    text-align: right;
+  }
+}
+@keyframes missingTo {
+  from {
+    text-align: right;
+  }
+}
+</style>
+<script>
+'use strict';
+
+const gTests = [
+  { desc: 'missing "from" keyframe',
+    animationName: 'missingFrom',
+    expected: [{ property: 'text-align',
+                 values: [valueFormat(0, undefined, 'replace', 'ease'),
+                          valueFormat(1, 'right',   'replace')] } ]
+  },
+  { desc: 'missing "to" keyframe',
+    animationName: 'missingTo',
+    expected: [{ property: 'text-align',
+                 values: [valueFormat(0, 'right',   'replace', 'ease'),
+                          valueFormat(1, undefined, 'replace')] } ]
+  },
+  { desc: 'missing "from" and "to" keyframes',
+    animationName: 'missingBoth',
+    expected: [{ property: 'text-align',
+                 values: [valueFormat(0,  undefined, 'replace', 'ease'),
+                          valueFormat(.5, 'right',   'replace', 'ease'),
+                          valueFormat(1,  undefined, 'replace')] } ]
+  },
+];
+
+SpecialPowers.pushPrefEnv(
+  { set: [["dom.animations-api.core.enabled", true]] },
+  function() {
+    gTests.forEach(function(subtest) {
+      test(function(t) {
+        const div = addDiv(t);
+        div.style.animation = `${ subtest.animationName } 1000s`;
+        const animation = div.getAnimations()[0];
+        assert_properties_equal(animation.effect.getProperties(),
+                                subtest.expected);
+      }, subtest.desc);
+    });
+
+    done();
+  }
+);
+
+</script>
+</body>
--- a/dom/animation/test/chrome/test_restyles.html
+++ b/dom/animation/test/chrome/test_restyles.html
@@ -846,12 +846,28 @@ waitForAllPaints(function() {
     var markers = yield observeStyling(5);
 
     is(markers.length, 0,
        'Discrete animation running on the main-thread in an out-of-view ' +
        'element should never cause restyles');
     yield ensureElementRemoval(div);
   });
 
+  add_task(function* no_restyling_while_computed_timing_is_not_changed() {
+    var div = addDiv(null);
+    var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
+                                { duration: 100 * MS_PER_SEC,
+                                  easing: 'step-end' });
+
+    yield animation.ready;
+
+    var markers = yield observeStyling(5);
+
+    is(markers.length, 0,
+       'Animation running on the main-thread while computed timing is not ' +
+       'changed should never cause restyles');
+    yield ensureElementRemoval(div);
+  });
+
 });
 
 </script>
 </body>
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -50,16 +50,65 @@ function assert_matrix_equals(actual, ex
   for (var i = 0; i < actualMatrixArray.length; i++) {
     assert_approx_equals(actualMatrixArray[i], expectedMatrixArray[i], 0.01,
       'Matrix array should be equal (got \'' + expected + '\' and \'' + actual +
       '\'): ' + description);
   }
 }
 
 /**
+ * Compare given values which are same format of
+ * KeyframeEffectReadonly::GetProperties.
+ */
+function assert_properties_equal(actual, expected) {
+  assert_equals(actual.length, expected.length);
+
+  const compareProperties = (a, b) =>
+    a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
+
+  const sortedActual   = actual.sort(compareProperties);
+  const sortedExpected = expected.sort(compareProperties);
+
+  const serializeValues = values =>
+    values.map(value =>
+      '{ ' +
+          [ 'offset', 'value', 'easing', 'composite' ].map(
+            member => `${member}: ${value[member]}`
+          ).join(', ') +
+                      ' }')
+          .join(', ');
+
+  for (let i = 0; i < sortedActual.length; i++) {
+    assert_equals(sortedActual[i].property,
+                  sortedExpected[i].property,
+                  'CSS property name should match');
+    assert_equals(serializeValues(sortedActual[i].values),
+                  serializeValues(sortedExpected[i].values),
+                  `Values arrays do not match for `
+                  + `${sortedActual[i].property} property`);
+  }
+}
+
+/**
+ * Construct a object which is same to a value of
+ * KeyframeEffectReadonly::GetProperties().
+ * The method returns undefined as a value in case of missing keyframe.
+ * Therefor, we can use undefined for |value| and |easing| parameter.
+ * @param offset - keyframe offset. e.g. 0.1
+ * @param value - any keyframe value. e.g. undefined '1px', 'center', 0.5
+ * @param composite - 'replace', 'add', 'accumulate'
+ * @param easing - e.g. undefined, 'linear', 'ease' and so on
+ * @return Object -
+ *   e.g. { offset: 0.1, value: '1px', composite: 'replace', easing: 'ease'}
+ */
+function valueFormat(offset, value, composite, easing) {
+  return { offset: offset, value: value, easing: easing, composite: composite };
+}
+
+/**
  * Appends a div to the document body and creates an animation on the div.
  * NOTE: This function asserts when trying to create animations with durations
  * shorter than 100s because the shorter duration may cause intermittent
  * failures.  If you are not sure how long it is suitable, use 100s; it's
  * long enough but shorter than our test framework timeout (330s).
  * If you really need to use shorter durations, use animate() function directly.
  *
  * @param t  The testharness.js Test object. If provided, this will be used
--- a/dom/base/ProcessSelector.js
+++ b/dom/base/ProcessSelector.js
@@ -7,30 +7,20 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import('resource://gre/modules/Services.jsm');
 
 const BASE_PREF = "dom.ipc.processCount"
 const PREF_BRANCH = BASE_PREF + ".";
 
 // Utilities:
 function getMaxContentParents(processType) {
-  let maxContentParents = -1;
-  try {
-    maxContentParents = Services.prefs.getIntPref(PREF_BRANCH + processType);
-  } catch (e) {
-    // Pref probably didn't exist, get the default number of processes.
-    try {
-      maxContentParents = Services.prefs.getIntPref(BASE_PREF);
-    } catch (e) {
-      // No prefs? That's odd, use only one process.
-      maxContentParents = 1;
-    }
-  }
-
-  return maxContentParents;
+  // If the pref doesn't exist, get the default number of processes.
+  // If there's no pref, use only one process.
+  return Services.prefs.getIntPref(PREF_BRANCH + processType,
+                                   Services.prefs.getIntPref(BASE_PREF, 1));
 }
 
 // Fills up aProcesses until max and then selects randomly from the available
 // ones.
 function RandomSelector() {
 }
 
 RandomSelector.prototype = {
@@ -53,10 +43,41 @@ RandomSelector.prototype = {
 
       curIdx = (curIdx + 1) % maxContentParents;
     } while (curIdx !== startIdx);
 
     return Ci.nsIContentProcessProvider.NEW_PROCESS;
   },
 };
 
-var components = [RandomSelector];
+// Fills up aProcesses until max and then selects one from the available
+// ones that host the least number of tabs.
+function MinTabSelector() {
+}
+
+MinTabSelector.prototype = {
+  classID:          Components.ID("{2dc08eaf-6eef-4394-b1df-a3a927c1290b}"),
+  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentProcessProvider]),
+
+  provideProcess(aType, aOpener, aProcesses, aCount) {
+    let maxContentParents = getMaxContentParents(aType);
+    if (aCount < maxContentParents) {
+      return Ci.nsIContentProcessProvider.NEW_PROCESS;
+    }
+
+    let min = Number.MAX_VALUE;
+    let candidate = Ci.nsIContentProcessProvider.NEW_PROCESS;
+
+    for (let i = 0; i < maxContentParents; i++) {
+      let process = aProcesses[i];
+      let tabCount = process.tabCount;
+      if (process.opener === aOpener && tabCount < min) {
+        min = tabCount;
+        candidate = i;
+      }
+    }
+
+    return candidate;
+  },
+};
+
+var components = [RandomSelector, MinTabSelector];
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/dom/base/ProcessSelector.manifest
+++ b/dom/base/ProcessSelector.manifest
@@ -1,2 +1,3 @@
 component {c616fcfd-9737-41f1-aa74-cee72a38f91b} ProcessSelector.js
-contract @mozilla.org/ipc/processselector;1 {c616fcfd-9737-41f1-aa74-cee72a38f91b}
+component {2dc08eaf-6eef-4394-b1df-a3a927c1290b} ProcessSelector.js
+contract @mozilla.org/ipc/processselector;1 {2dc08eaf-6eef-4394-b1df-a3a927c1290b}
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3635,36 +3635,37 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMEle
       Layer* layer =
         FrameLayerBuilder::GetDedicatedLayer(frame,
                                              nsDisplayItem::TYPE_OPACITY);
       if (layer) {
         ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
         if (forwarder && forwarder->HasShadowManager()) {
           float value;
           bool hadAnimatedOpacity;
-          forwarder->GetShadowManager()->SendGetAnimationOpacity(
-            layer->AsShadowableLayer()->GetShadow(),
-            &value, &hadAnimatedOpacity);
+          forwarder->GetShadowManager()->
+            SendGetAnimationOpacity(layer->GetCompositorAnimationsId(),
+                                    &value,
+                                    &hadAnimatedOpacity);
 
           if (hadAnimatedOpacity) {
             cssValue = new nsROCSSPrimitiveValue;
             cssValue->SetNumber(value);
           }
         }
       }
     } else if (aProperty.EqualsLiteral("transform")) {
       Layer* layer =
         FrameLayerBuilder::GetDedicatedLayer(frame,
                                              nsDisplayItem::TYPE_TRANSFORM);
       if (layer) {
         ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
         if (forwarder && forwarder->HasShadowManager()) {
           MaybeTransform transform;
-          forwarder->GetShadowManager()->SendGetAnimationTransform(
-            layer->AsShadowableLayer()->GetShadow(), &transform);
+          forwarder->GetShadowManager()->
+            SendGetAnimationTransform(layer->GetCompositorAnimationsId(), &transform);
           if (transform.type() == MaybeTransform::TMatrix4x4) {
             Matrix4x4 matrix = transform.get_Matrix4x4();
             cssValue = nsComputedDOMStyle::MatrixToCSSValue(matrix);
           }
         }
       }
     }
   }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2403,18 +2403,23 @@ nsGlobalWindow::WouldReuseInnerWindow(ns
   if (!mDoc || !aNewDocument) {
     return false;
   }
 
   if (!mDoc->IsInitialDocument()) {
     return false;
   }
 
-  NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()),
-               "How'd this happen?");
+#ifdef DEBUG
+{
+  nsCOMPtr<nsIURI> uri;
+  mDoc->GetDocumentURI()->CloneIgnoringRef(getter_AddRefs(uri));
+  NS_ASSERTION(NS_IsAboutBlank(uri), "How'd this happen?");
+}
+#endif
 
   // Great, we're the original document, check for one of the other
   // conditions.
 
   if (mDoc == aNewDocument) {
     return true;
   }
 
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -954,16 +954,24 @@ public:
         if (hasAttr) {
           return true;
         }
       }
     }
     return false;
   }
 
+  // Returns true if this element is native-anonymous scrollbar content.
+  bool IsNativeScrollbarContent() const {
+    return IsNativeAnonymous() &&
+           IsAnyOfXULElements(nsGkAtoms::scrollbar,
+                              nsGkAtoms::resizer,
+                              nsGkAtoms::scrollcorner);
+  }
+
   // Overloaded from nsINode
   virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
 
   virtual nsresult GetEventTargetParent(
                      mozilla::EventChainPreVisitor& aVisitor) override;
 
   virtual bool IsPurple() = 0;
   virtual void RemovePurple() = 0;
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -959,21 +959,17 @@ BrowserElementChild.prototype = {
     let maxHeight = data.json.args.height;
     let mimeType = data.json.args.mimeType;
     let domRequestID = data.json.id;
 
     let takeScreenshotClosure = function() {
       self._takeScreenshot(maxWidth, maxHeight, mimeType, domRequestID);
     };
 
-    let maxDelayMS = 2000;
-    try {
-      maxDelayMS = Services.prefs.getIntPref('dom.browserElement.maxScreenshotDelayMS');
-    }
-    catch(e) {}
+    let maxDelayMS = Services.prefs.getIntPref('dom.browserElement.maxScreenshotDelayMS', 2000);
 
     // Try to wait for the event loop to go idle before we take the screenshot,
     // but once we've waited maxDelayMS milliseconds, go ahead and take it
     // anyway.
     Cc['@mozilla.org/message-loop;1'].getService(Ci.nsIMessageLoop).postIdleTask(
       takeScreenshotClosure, maxDelayMS);
   },
 
@@ -1644,20 +1640,17 @@ BrowserElementChild.prototype = {
               let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1']
                                        .getService(Ci.nsINSSErrorsService);
               if (nssErrorsService.getErrorClass(status)
                     == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
                 // XXX Is there a point firing the event if the error page is not
                 // certerror? If yes, maybe we should add a property to the
                 // event to to indicate whether there is a custom page. That would
                 // let the embedder have more control over the desired behavior.
-                let errorPage = null;
-                try {
-                  errorPage = Services.prefs.getCharPref(CERTIFICATE_ERROR_PAGE_PREF);
-                } catch (e) {}
+                let errorPage = Services.prefs.getCharPref(CERTIFICATE_ERROR_PAGE_PREF, "");
 
                 if (errorPage == 'certerror') {
                   sendAsyncMsg('error', { type: 'certerror' });
                   return;
                 }
               }
             } catch (e) {}
 
--- a/dom/indexedDB/test/test_globalObjects_other.xul
+++ b/dom/indexedDB/test/test_globalObjects_other.xul
@@ -26,24 +26,18 @@
     AddonManager.getAddonByID("indexedDB-test@mozilla.org",
                               grabEventAndContinueHandler);
     let addon = yield undefined;
     addon.uninstall();
 
     Cu.import("resource://gre/modules/Services.jsm");
     for (var stage of [ "install", "startup", "shutdown", "uninstall" ]) {
       for (var symbol of [ "IDBKeyRange", "indexedDB" ]) {
-        let pref;
-        try {
-          pref = Services.prefs.getBoolPref("indexeddbtest.bootstrap." + stage +
-                                            "." + symbol);
-        }
-        catch(ex) {
-          pref = false;
-        }
+        let pref = Services.prefs.getBoolPref("indexeddbtest.bootstrap." + stage +
+                                              "." + symbol, false);
         ok(pref, "Symbol '" + symbol + "' present during '" + stage + "'");
       }
     }
 
     finishTest();
     yield undefined;
   }
 
--- a/dom/interfaces/base/nsIContentProcess.idl
+++ b/dom/interfaces/base/nsIContentProcess.idl
@@ -23,16 +23,21 @@ interface nsIContentProcessInfo : nsISup
   readonly attribute int32_t processId;
 
   /**
    * This content process's opener.
    */
   readonly attribute nsIContentProcessInfo opener;
 
   /**
+   * Number of opened tabs living in this content process.
+   */
+  readonly attribute int32_t tabCount;
+
+  /**
    * The process manager for this ContentParent (so a process message manager
    * as opposed to a frame message manager.
    */
   readonly attribute nsIMessageSender messageManager;
 };
 
 [scriptable, uuid(83ffb063-5f65-4c45-ae07-3f553e0809bb)]
 interface nsIContentProcessProvider : nsISupports
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -598,20 +598,16 @@ ContentChild::Init(MessageLoop* aIOLoop,
 #ifdef NS_PRINTING
   // Force the creation of the nsPrintingProxy so that it's IPC counterpart,
   // PrintingParent, is always available for printing initiated from the parent.
   RefPtr<nsPrintingProxy> printingProxy = nsPrintingProxy::GetInstance();
 #endif
 
   SetProcessName(NS_LITERAL_STRING("Web Content"), true);
 
-  nsTArray<mozilla::dom::GfxInfoFeatureStatus> featureStatus;
-  SendGetGfxInfoFeatureStatus(&featureStatus);
-  GfxInfoBase::SetFeatureStatus(featureStatus);
-
   return true;
 }
 
 void
 ContentChild::SetProcessName(const nsAString& aName, bool aDontOverride)
 {
   if (!mCanOverrideProcessName) {
     return;
@@ -1028,16 +1024,18 @@ ContentChild::InitXPCOM(const XPCOMInitD
   }
 
   // The stylesheet cache is not ready yet. Store this URL for future use.
   nsCOMPtr<nsIURI> ucsURL = DeserializeURI(aXPCOMInit.userContentSheetURL());
   nsLayoutStylesheetCache::SetUserContentCSSURL(ucsURL);
 
   // This will register cross-process observer.
   mozilla::dom::time::InitializeDateCacheCleaner();
+
+  GfxInfoBase::SetFeatureStatus(aXPCOMInit.gfxFeatureStatus());
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvRequestMemoryReport(const uint32_t& aGeneration,
                                       const bool& aAnonymize,
                                       const bool& aMinimizeMemoryUsage,
                                       const MaybeFileDesc& aDMDFile)
 {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -501,16 +501,29 @@ ScriptableCPInfo::GetOpener(nsIContentPr
   if (ContentParent* opener = mContentParent->Opener()) {
     nsCOMPtr<nsIContentProcessInfo> info = opener->ScriptableHelper();
     info.forget(aInfo);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ScriptableCPInfo::GetTabCount(int32_t* aTabCount)
+{
+  if (!mContentParent) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  *aTabCount = cpm->GetTabParentCountByProcessId(mContentParent->ChildID());
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 ScriptableCPInfo::GetMessageManager(nsIMessageSender** aMessenger)
 {
   *aMessenger = nullptr;
   if (!mContentParent) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsCOMPtr<nsIMessageSender> manager = mContentParent->GetMessageManager();
@@ -752,33 +765,38 @@ ContentParent::ReleaseCachedProcesses()
     // Make sure we don't select this process for new tabs.
     cp->MarkAsDead();
     // Make sure that this process is no longer accessible from JS by its message manager.
     cp->ShutDownMessageManager();
   }
 }
 
 /*static*/ already_AddRefed<ContentParent>
-ContentParent::RandomSelect(const nsTArray<ContentParent*>& aContentParents,
+ContentParent::MinTabSelect(const nsTArray<ContentParent*>& aContentParents,
                             ContentParent* aOpener, int32_t aMaxContentParents)
 {
   uint32_t maxSelectable = std::min(static_cast<uint32_t>(aContentParents.Length()),
                                     static_cast<uint32_t>(aMaxContentParents));
-  uint32_t startIdx = rand() % maxSelectable;
-  uint32_t currIdx = startIdx;
-  do {
-    RefPtr<ContentParent> p = aContentParents[currIdx];
+  uint32_t min = INT_MAX;
+  RefPtr<ContentParent> candidate;
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+
+  for (uint32_t i = 0; i < maxSelectable; i++) {
+    ContentParent* p = aContentParents[i];
     NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sBrowserContentParents?");
     if (p->mOpener == aOpener) {
-      return p.forget();
+      uint32_t tabCount = cpm->GetTabParentCountByProcessId(p->ChildID());
+      if (tabCount < min) {
+        candidate = p;
+        min = tabCount;
+      }
     }
-    currIdx = (currIdx + 1) % maxSelectable;
-  } while (currIdx != startIdx);
-
-  return nullptr;
+  }
+
+  return candidate.forget();
 }
 
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
                                           ProcessPriority aPriority,
                                           ContentParent* aOpener)
 {
   nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
@@ -811,17 +829,17 @@ ContentParent::GetNewOrUsedBrowserProces
         return retval.forget();
       }
     } else {
       // If there was a problem with the JS chooser, fall back to a random
       // selection.
       NS_WARNING("nsIContentProcessProvider failed to return a process");
       RefPtr<ContentParent> random;
       if (contentParents.Length() >= maxContentParents &&
-          (random = RandomSelect(contentParents, aOpener, maxContentParents))) {
+          (random = MinTabSelect(contentParents, aOpener, maxContentParents))) {
         return random.forget();
       }
     }
 
     // Try to take the preallocated process only for the default process type.
     RefPtr<ContentParent> p;
     if (aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) &&
         (p = PreallocatedProcessManager::Take())) {
@@ -2169,16 +2187,30 @@ ContentParent::InitInternal(ProcessPrior
   // send the file URL instead.
   StyleSheet* ucs = nsLayoutStylesheetCache::For(StyleBackendType::Gecko)->UserContentSheet();
   if (ucs) {
     SerializeURI(ucs->GetSheetURI(), xpcomInit.userContentSheetURL());
   } else {
     SerializeURI(nullptr, xpcomInit.userContentSheetURL());
   }
 
+  nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+  if (gfxInfo) {
+    for (int32_t i = 1; i <= nsIGfxInfo::FEATURE_MAX_VALUE; ++i) {
+      int32_t status = 0;
+      nsAutoCString failureId;
+      gfxInfo->GetFeatureStatus(i, failureId, &status);
+      dom::GfxInfoFeatureStatus gfxFeatureStatus;
+      gfxFeatureStatus.feature() = i;
+      gfxFeatureStatus.status() = status;
+      gfxFeatureStatus.failureId() = failureId;
+      xpcomInit.gfxFeatureStatus().AppendElement(gfxFeatureStatus);
+    }
+  }
+
   Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache);
 
   if (aSendRegisteredChrome) {
     nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
     nsChromeRegistryChrome* chromeRegistry =
       static_cast<nsChromeRegistryChrome*>(registrySvc.get());
     chromeRegistry->SendRegisteredChrome(this);
   }
@@ -4013,35 +4045,16 @@ ContentParent::RecvRecordingDeviceEvents
                          aRecordingStatus.get());
   } else {
     NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-ContentParent::RecvGetGfxInfoFeatureStatus(nsTArray<mozilla::dom::GfxInfoFeatureStatus>* aFS)
-{
-  nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
-  if (!gfxInfo) {
-    return IPC_OK();
-  }
-
-  for (int32_t i = 1; i <= nsIGfxInfo::FEATURE_MAX_VALUE; ++i) {
-    int32_t status = 0;
-    nsAutoCString failureId;
-    gfxInfo->GetFeatureStatus(i, failureId, &status);
-    mozilla::dom::GfxInfoFeatureStatus fs(i, status, failureId);
-    aFS->AppendElement(Move(fs));
-  }
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 ContentParent::RecvAddIdleObserver(const uint64_t& aObserver,
                                    const uint32_t& aIdleTimeInS)
 {
   nsresult rv;
   nsCOMPtr<nsIIdleService> idleService =
     do_GetService("@mozilla.org/widget/idleservice;1", &rv);
   NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this));
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -154,17 +154,17 @@ public:
   static void ReleaseCachedProcesses();
 
   /**
    * Picks a random content parent from |aContentParents| with a given |aOpener|
    * respecting the index limit set by |aMaxContentParents|.
    * Returns null if non available.
    */
   static already_AddRefed<ContentParent>
-  RandomSelect(const nsTArray<ContentParent*>& aContentParents,
+  MinTabSelect(const nsTArray<ContentParent*>& aContentParents,
                ContentParent* aOpener,
                int32_t maxContentParents);
 
   /**
    * Get or create a content process for:
    * 1. browser iframe
    * 2. remote xul <browser>
    * 3. normal iframe
@@ -464,18 +464,16 @@ public:
 
   virtual PRemoteSpellcheckEngineParent* AllocPRemoteSpellcheckEngineParent() override;
 
   virtual mozilla::ipc::IPCResult RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
                                                             const nsString& aPageURL,
                                                             const bool& aIsAudio,
                                                             const bool& aIsVideo) override;
 
-  virtual mozilla::ipc::IPCResult RecvGetGfxInfoFeatureStatus(nsTArray<mozilla::dom::GfxInfoFeatureStatus>* aFS) override;
-
   bool CycleCollectWithLogs(bool aDumpAllTraces,
                             nsICycleCollectorLogSink* aSink,
                             nsIDumpGCAndCCLogsCallback* aCallback);
 
   virtual PBlobParent*
   SendPBlobConstructor(PBlobParent* aActor,
                        const BlobConstructorParams& aParams) override;
 
--- a/dom/ipc/ContentProcessManager.cpp
+++ b/dom/ipc/ContentProcessManager.cpp
@@ -349,10 +349,23 @@ ContentProcessManager::GetTabParentsByPr
       remoteFrameIter != iter->second.mRemoteFrames.end();
       ++remoteFrameIter) {
     tabIdList.AppendElement(remoteFrameIter->first);
   }
 
   return Move(tabIdList);
 }
 
+uint32_t
+ContentProcessManager::GetTabParentCountByProcessId(const ContentParentId& aChildCpId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  auto iter = mContentParentMap.find(aChildCpId);
+  if (NS_WARN_IF(iter == mContentParentMap.end())) {
+    return 0;
+  }
+
+  return iter->second.mRemoteFrames.size();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentProcessManager.h
+++ b/dom/ipc/ContentProcessManager.h
@@ -113,16 +113,23 @@ public:
   /**
    * Get all TabParents' Ids managed by the givent content process.
    * Return empty array when TabParent couldn't be found via aChildCpId
    */
   nsTArray<TabId>
   GetTabParentsByProcessId(const ContentParentId& aChildCpId);
 
   /**
+   * Get the number of TabParents managed by the givent content process.
+   * Return 0 when TabParent couldn't be found via aChildCpId.
+   */
+  uint32_t
+  GetTabParentCountByProcessId(const ContentParentId& aChildCpId);
+
+  /**
    * Get the TabParent by the given content process and tab id.
    * Return nullptr when TabParent couldn't be found via aChildCpId
    * and aChildTabId.
    * (or probably because the TabParent is not in the chrome process)
    */
   already_AddRefed<TabParent>
   GetTabParentByProcessAndTabId(const ContentParentId& aChildCpId,
                                 const TabId& aChildTabId);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -345,37 +345,38 @@ struct GMPAPITags
 
 struct GMPCapabilityData
 {
     nsCString name;
     nsCString version;
     GMPAPITags[] capabilities;
 };
 
+struct GfxInfoFeatureStatus
+{
+    int32_t feature;
+    int32_t status;
+    nsCString failureId;
+};
+
 struct XPCOMInitData
 {
     bool isOffline;
     bool isConnected;
     int32_t captivePortalState;
     bool isLangRTL;
     bool haveBidiKeyboards;
     nsString[] dictionaries;
     ClipboardCapabilities clipboardCaps;
     DomainPolicyClone domainPolicy;
     /* used on MacOSX only */
     FontFamilyListEntry[] fontFamilies;
     OptionalURIParams userContentSheetURL;
     PrefSetting[] prefs;
-};
-
-struct GfxInfoFeatureStatus
-{
-    int32_t feature;
-    int32_t status;
-    nsCString failureId;
+    GfxInfoFeatureStatus[] gfxFeatureStatus;
 };
 
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
@@ -959,18 +960,16 @@ parent:
      * @param isAudio recording start with microphone
      * @param isVideo recording start with camera
      */
     async RecordingDeviceEvents(nsString recordingStatus,
                                 nsString pageURL,
                                 bool isAudio,
                                 bool isVideo);
 
-    sync GetGfxInfoFeatureStatus() returns (GfxInfoFeatureStatus[] features);
-
     // Graphics errors
     async GraphicsError(nsCString aError);
 
     // Driver crash guards. aGuardType must be a member of CrashGuardType.
     sync BeginDriverCrashGuard(uint32_t aGuardType) returns (bool crashDetected);
     sync EndDriverCrashGuard(uint32_t aGuardType);
 
     async AddIdleObserver(uint64_t observerId, uint32_t idleTimeInS);
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -392,17 +392,17 @@ SimpleTimer::Create(nsIRunnable* aTask, 
 
 void
 LogToBrowserConsole(const nsAString& aMsg)
 {
   if (!NS_IsMainThread()) {
     nsString msg(aMsg);
     nsCOMPtr<nsIRunnable> task =
       NS_NewRunnableFunction([msg]() { LogToBrowserConsole(msg); });
-    NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL);
+    SystemGroup::Dispatch("LogToBrowserConsole", TaskCategory::Other, task.forget());
     return;
   }
   nsCOMPtr<nsIConsoleService> console(
     do_GetService("@mozilla.org/consoleservice;1"));
   if (!console) {
     NS_WARNING("Failed to log message to console.");
     return;
   }
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -89,17 +89,18 @@ public:
   {}
 
   // Main thread only.
   // Loads the CDM corresponding to mKeySystem.
   // Calls MediaKeys::OnCDMCreated() when the CDM is created.
   virtual void Init(PromiseId aPromiseId,
                     const nsAString& aOrigin,
                     const nsAString& aTopLevelOrigin,
-                    const nsAString& aName) = 0;
+                    const nsAString& aName,
+                    nsIEventTarget* aMainThread) = 0;
 
   virtual void OnSetDecryptorId(uint32_t aId) {}
 
   // Main thread only.
   // Uses the CDM to create a key session.
   // Calls MediaKeys::OnSessionActivated() when session is created.
   // Assumes ownership of (Move()s) aInitData's contents.
   virtual void CreateSession(uint32_t aCreateSessionToken,
@@ -254,16 +255,19 @@ protected:
 
   // Our reference back to the MediaKeys object.
   // WARNING: This is a non-owning reference that is cleared by MediaKeys
   // destructor. only use on main thread, and always nullcheck before using!
   MainThreadOnlyRawPtr<dom::MediaKeys> mKeys;
 
   const nsString mKeySystem;
 
+  // The main thread associated with the root document. Must be set in Init().
+  nsCOMPtr<nsIEventTarget> mMainThread;
+
   // Onwer specified thread. e.g. Gecko Media Plugin thread.
   // All interactions with the out-of-process EME plugin must come from this thread.
   RefPtr<nsIThread> mOwnerThread;
 
   nsCString mNodeId;
 
   CDMCaps mCapabilites;
 
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -424,17 +424,18 @@ MediaKeys::Init(ErrorResult& aRv)
   // here, and hold a self-reference until that promise is resolved or
   // rejected.
   MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
   mCreatePromiseId = StorePromise(promise);
   AddRef();
   mProxy->Init(mCreatePromiseId,
                NS_ConvertUTF8toUTF16(origin),
                NS_ConvertUTF8toUTF16(topLevelOrigin),
-               KeySystemToGMPName(mKeySystem));
+               KeySystemToGMPName(mKeySystem),
+               top->GetExtantDoc()->EventTargetFor(TaskCategory::Other));
 
   return promise.forget();
 }
 
 void
 MediaKeys::OnCDMCreated(PromiseId aId, const uint32_t aPluginId)
 {
   RefPtr<DetailedPromise> promise(RetrievePromise(aId));
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -41,26 +41,29 @@ MediaDrmCDMProxy::~MediaDrmCDMProxy()
 {
   MOZ_COUNT_DTOR(MediaDrmCDMProxy);
 }
 
 void
 MediaDrmCDMProxy::Init(PromiseId aPromiseId,
                        const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
-                       const nsAString& aName)
+                       const nsAString& aName,
+                       nsIEventTarget* aMainThread)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
   EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
           NS_ConvertUTF16toUTF8(aOrigin).get(),
           NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
           NS_ConvertUTF16toUTF8(aName).get());
 
+  mMainThread = aMainThread;
+
   // Create a thread to work with cdm.
   if (!mOwnerThread) {
     nsresult rv = NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
     if (NS_FAILED(rv)) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                     NS_LITERAL_CSTRING("Couldn't create CDM thread MediaDrmCDMProxy::Init"));
       return;
     }
@@ -303,17 +306,17 @@ MediaDrmCDMProxy::RejectPromise(PromiseI
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
       mKeys->RejectPromise(aId, aCode, aReason);
     }
   } else {
     nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
                                                      aReason));
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 void
 MediaDrmCDMProxy::ResolvePromise(PromiseId aId)
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
@@ -321,17 +324,17 @@ MediaDrmCDMProxy::ResolvePromise(Promise
     } else {
       NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
     }
   } else {
     nsCOMPtr<nsIRunnable> task;
     task = NewRunnableMethod<PromiseId>(this,
                                         &MediaDrmCDMProxy::ResolvePromise,
                                         aId);
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 const nsString&
 MediaDrmCDMProxy::KeySystem() const
 {
   return mKeySystem;
 }
@@ -403,17 +406,17 @@ MediaDrmCDMProxy::md_Init(uint32_t aProm
   MOZ_ASSERT(mCDM);
 
   mCallback.reset(new MediaDrmCDMCallbackProxy(this));
   mCDM->Init(mCallback.get());
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<uint32_t>(this,
                                 &MediaDrmCDMProxy::OnCDMCreated,
                                 aPromiseId));
-  NS_DispatchToMainThread(task);
+  mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
 }
 
 void
 MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData)
 {
   MOZ_ASSERT(IsOnOwnerThread());
 
   if (!mCDM) {
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -32,17 +32,18 @@ public:
   MediaDrmCDMProxy(dom::MediaKeys* aKeys,
                    const nsAString& aKeySystem,
                    bool aDistinctiveIdentifierRequired,
                    bool aPersistentStateRequired);
 
   void Init(PromiseId aPromiseId,
             const nsAString& aOrigin,
             const nsAString& aTopLevelOrigin,
-            const nsAString& aGMPName) override;
+            const nsAString& aGMPName,
+            nsIEventTarget* aMainThread) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
                      MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
 
   void LoadSession(PromiseId aPromiseId,
--- a/dom/media/gmp/GMPCDMProxy.cpp
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -47,25 +47,28 @@ GMPCDMProxy::~GMPCDMProxy()
 {
   MOZ_COUNT_DTOR(GMPCDMProxy);
 }
 
 void
 GMPCDMProxy::Init(PromiseId aPromiseId,
                   const nsAString& aOrigin,
                   const nsAString& aTopLevelOrigin,
-                  const nsAString& aGMPName)
+                  const nsAString& aGMPName,
+                  nsIEventTarget* aMainThread)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
   EME_LOG("GMPCDMProxy::Init (%s, %s)",
           NS_ConvertUTF16toUTF8(aOrigin).get(),
           NS_ConvertUTF16toUTF8(aTopLevelOrigin).get());
 
+  mMainThread = aMainThread;
+
   nsCString pluginVersion;
   if (!mOwnerThread) {
     nsCOMPtr<mozIGeckoMediaPluginService> mps =
       do_GetService("@mozilla.org/gecko-media-plugin-service;1");
     if (!mps) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                     NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::Init"));
       return;
@@ -136,17 +139,17 @@ GMPCDMProxy::gmp_InitDone(GMPDecryptorPr
 void GMPCDMProxy::OnSetDecryptorId(uint32_t aId)
 {
   MOZ_ASSERT(mCreatePromiseId);
   mDecryptorId = aId;
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<uint32_t>(this,
                                 &GMPCDMProxy::OnCDMCreated,
                                 mCreatePromiseId));
-  NS_DispatchToMainThread(task);
+  mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
 }
 
 class gmp_InitDoneCallback : public GetGMPDecryptorCallback
 {
 public:
   gmp_InitDoneCallback(GMPCDMProxy* aGMPCDMProxy,
                        UniquePtr<GMPCDMProxy::InitData>&& aData)
     : mGMPCDMProxy(aGMPCDMProxy),
@@ -513,17 +516,17 @@ GMPCDMProxy::RejectPromise(PromiseId aId
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
       mKeys->RejectPromise(aId, aCode, aReason);
     }
   } else {
     nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
                                                      aReason));
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 void
 GMPCDMProxy::ResolvePromise(PromiseId aId)
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
@@ -531,17 +534,17 @@ GMPCDMProxy::ResolvePromise(PromiseId aI
     } else {
       NS_WARNING("GMPCDMProxy unable to resolve promise!");
     }
   } else {
     nsCOMPtr<nsIRunnable> task;
     task = NewRunnableMethod<PromiseId>(this,
                                         &GMPCDMProxy::ResolvePromise,
                                         aId);
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 const nsCString&
 GMPCDMProxy::GetNodeId() const
 {
   return mNodeId;
 }
@@ -604,17 +607,20 @@ GMPCDMProxy::OnExpirationChange(const ns
                                 GMPTimestamp aExpiryTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
   RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
   if (session) {
-    session->SetExpiration(static_cast<double>(aExpiryTime));
+    // Expiry of 0 is interpreted as "never expire". See bug 1345341.
+    double t = (aExpiryTime == 0) ? std::numeric_limits<double>::quiet_NaN()
+                                  : static_cast<double>(aExpiryTime);
+    session->SetExpiration(t);
   }
 }
 
 void
 GMPCDMProxy::OnSessionClosed(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
--- a/dom/media/gmp/GMPCDMProxy.h
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -26,17 +26,18 @@ public:
               const nsAString& aKeySystem,
               GMPCrashHelper* aCrashHelper,
               bool aDistinctiveIdentifierRequired,
               bool aPersistentStateRequired);
 
   void Init(PromiseId aPromiseId,
             const nsAString& aOrigin,
             const nsAString& aTopLevelOrigin,
-            const nsAString& aGMPName) override;
+            const nsAString& aGMPName,
+            nsIEventTarget* aMainThread) override;
 
   void OnSetDecryptorId(uint32_t aId) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
                      dom::MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -409,21 +409,17 @@ WidevineDecryptor::OnExpirationChange(co
                                       Time aNewExpiryTime)
 {
   if (!mCallback) {
     CDM_LOG("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback",
             aSessionId, aNewExpiryTime);
     return;
   }
   CDM_LOG("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime);
-  GMPTimestamp expiry = ToGMPTime(aNewExpiryTime);
-  if (aNewExpiryTime == 0) {
-    return;
-  }
-  mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry);
+  mCallback->ExpirationChange(aSessionId, aSessionIdSize, ToGMPTime(aNewExpiryTime));
 }
 
 void
 WidevineDecryptor::OnSessionClosed(const char* aSessionId,
                                    uint32_t aSessionIdSize)
 {
   if (!mCallback) {
     CDM_LOG("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId);
--- a/dom/media/gtest/GMPTestMonitor.h
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -31,17 +31,19 @@ private:
   {
     MOZ_ASSERT(NS_IsMainThread());
     mFinished = true;
   }
 
 public:
   void SetFinished()
   {
-    NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this,
-                                                                &GMPTestMonitor::MarkFinished));
+    mozilla::SystemGroup::Dispatch(
+      "GMPTestMonitor::SetFinished",
+      mozilla::TaskCategory::Other,
+      mozilla::NewNonOwningRunnableMethod(this, &GMPTestMonitor::MarkFinished));
   }
 
 private:
   bool mFinished;
 };
 
 #endif // __GMPTestMonitor_h__
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -422,22 +422,23 @@ private:
 NS_IMPL_ISUPPORTS(ClearGMPStorageTask, nsIRunnable, nsIObserver)
 
 static void
 ClearGMPStorage(already_AddRefed<nsIRunnable> aContinuation,
                 nsIThread* aTarget, PRTime aSince = -1)
 {
   RefPtr<ClearGMPStorageTask> task(
     new ClearGMPStorageTask(Move(aContinuation), aTarget, aSince));
-  NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+  SystemGroup::Dispatch("ClearGMPStorage", TaskCategory::Other, task.forget());
 }
 
 static void
 SimulatePBModeExit()
 {
+  // SystemGroup::EventTargetFor() doesn't support NS_DISPATCH_SYNC.
   NS_DispatchToMainThread(new NotifyObserversTask("last-pb-context-exited"), NS_DISPATCH_SYNC);
 }
 
 class TestGetNodeIdCallback : public GetNodeIdCallback
 {
 public:
   TestGetNodeIdCallback(nsCString& aNodeId, nsresult& aResult)
     : mNodeId(aNodeId),
@@ -780,17 +781,20 @@ class GMPStorageTest : public GMPDecrypt
 
     UniquePtr<NodeInfo> siteInfo(
         new NodeInfo(NS_LITERAL_CSTRING("http://example1.com"),
                      pattern));
     // Collect nodeIds that are expected to remain for later comparison.
     EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"),
                            NodeIdCollector(siteInfo.get()));
     // Invoke "Forget this site" on the main thread.
-    NS_DispatchToMainThread(NewRunnableMethod<UniquePtr<NodeInfo>&&>(
+    SystemGroup::Dispatch(
+      "TestForgetThisSite_Forget",
+      TaskCategory::Other,
+      NewRunnableMethod<UniquePtr<NodeInfo>&&>(
         this, &GMPStorageTest::TestForgetThisSite_Forget, Move(siteInfo)));
   }
 
   void TestForgetThisSite_Forget(UniquePtr<NodeInfo>&& aSiteInfo) {
     RefPtr<GeckoMediaPluginServiceParent> service =
         GeckoMediaPluginServiceParent::GetSingleton();
     service->ForgetThisSiteNative(NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget),
                                   aSiteInfo->mPattern);
@@ -1164,34 +1168,35 @@ class GMPStorageTest : public GMPDecrypt
     EXPECT_TRUE(!!mDecryptor);
     if (!mDecryptor) {
       return;
     }
     EXPECT_FALSE(mNodeId.IsEmpty());
     RefPtr<GMPShutdownObserver> task(
       new GMPShutdownObserver(NewRunnableMethod(this, &GMPStorageTest::Shutdown),
                               Move(aContinuation), mNodeId));
-    NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+    SystemGroup::Dispatch("GMPShutdownObserver", TaskCategory::Other, task.forget());
   }
 
   void Shutdown() {
     if (mDecryptor) {
       mDecryptor->Close();
       mDecryptor = nullptr;
       mNodeId = EmptyCString();
     }
   }
 
   void Dummy() {
   }
 
   void SetFinished() {
     mFinished = true;
     Shutdown();
-    NS_DispatchToMainThread(NewRunnableMethod(this, &GMPStorageTest::Dummy));
+    nsCOMPtr<nsIRunnable> task = NewRunnableMethod(this, &GMPStorageTest::Dummy);
+    SystemGroup::Dispatch("GMPStorageTest::Dummy", TaskCategory::Other, task.forget());
   }
 
   void SessionMessage(const nsCString& aSessionId,
                       mozilla::dom::MediaKeyMessageType aMessageType,
                       const nsTArray<uint8_t>& aMessage) override
   {
     MonitorAutoLock mon(mMonitor);
 
--- a/dom/media/test/test_buffered.html
+++ b/dom/media/test/test_buffered.html
@@ -9,60 +9,59 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462957">Mozilla Bug 462957</a>
 
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 // Test for Bug 462957; HTMLMediaElement.buffered.
 
 var manager = new MediaTestManager;
 
 function testBuffered(e) {
   var v = e.target;
-  v.removeEventListener('timeupdate', testBuffered);
-  
+
   // The whole media should be buffered...
   var b = v.buffered;
   is(b.length, 1, v._name + ": Should be buffered in one range");
   is(b.start(0), 0, v._name + ": First range start should be media start");
   ok(Math.abs(b.end(0) - v.duration) < 0.1, v._name + ": First range end should be media end");
 
   // Ensure INDEX_SIZE_ERR is thrown when we access outside the range
   var caught = false;
   try {
     b.start(-1);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under start bounds range");
-  
+
   caught = false;
   try {
     b.end(-1);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under end bounds range");
 
   caught = false;
   try {
     b.start(b.length);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over start bounds range");
-  
+
   caught = false;
   try {
     b.end(b.length);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over end bounds range");
 
@@ -72,48 +71,47 @@ function testBuffered(e) {
 
 function fetch(url, fetched_callback) {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.responseType = "blob";
 
   var loaded = function (event) {
     if (xhr.status == 200 || xhr.status == 206) {
+      ok(true, `${url}: Fetch succeeded, status=${xhr.status}`);
       // Request fulfilled. Note sometimes we get 206... Presumably because either
       // httpd.js or Necko cached the result.
       fetched_callback(window.URL.createObjectURL(xhr.response));
     } else {
-      ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+      ok(false, `${url}: Fetch failed, headers=${xhr.getAllResponseHeaders()}`);
     }
   };
 
   xhr.addEventListener("load", loaded);
   xhr.send();
 }
 
 function startTest(test, token) {
   // Fetch the media resource using XHR so we can be sure the entire
   // resource is loaded before we test buffered ranges. This ensures
   // we have deterministic behaviour.
   var onfetched = function(uri) {
     var v = document.createElement('video');
-    v.autoplay = true;
     v._token = token;
     v.src = uri;
     v._name = test.name;
     v._test = test;
-    v.addEventListener("timeupdate", testBuffered);
+    v.addEventListener("loadeddata", testBuffered, {once: true});
     document.body.appendChild(v);
   };
 
   manager.started(token);
   fetch(test.name, onfetched);
 }
 
 // Note: No need to set media test prefs, since we're using XHR to fetch
 // media data.
-SimpleTest.waitForExplicitFinish();
 manager.runTests(gSeekTests, startTest);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
+++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
@@ -43,22 +43,20 @@ function tryToCreateNodeOnClosedContext(
 
       expectException(function() {
         ctx[e.name].apply(ctx, e.args);
       }, DOMException.INVALID_STATE_ERR);
     });
 }
 
 function loadFile(url, callback) {
-  todo(false, "loadFile: " + url);
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.responseType = "arraybuffer";
   xhr.onload = function() {
-    todo(false, "loadFile: " + url + " calling callback...");
     callback(xhr.response);
   };
   xhr.send();
 }
 
 // createBuffer, createPeriodicWave and decodeAudioData should work on a context
 // that has `state` == "closed"
 function tryLegalOpeerationsOnClosedContext(ctx) {
@@ -388,23 +386,28 @@ function finish() {
     SimpleTest.finish();
   }
 }
 
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   var tests = [
-    testAudioContext,
     testOfflineAudioContext,
     testScriptProcessNodeSuspended,
     testMultiContextOutput,
     testMultiContextInput,
     testSuspendResumeEventLoop
   ];
+
+  // See Bug 1305136, many intermittent failures on Linux
+  if (!navigator.platform.startsWith("Linux")) {
+    tests.push(testAudioContext);
+  }
+
   remaining = tests.length;
   tests.forEach(function(f) { f() });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/presentation/PresentationDataChannelSessionTransport.js
+++ b/dom/presentation/PresentationDataChannelSessionTransport.js
@@ -104,23 +104,17 @@ PresentationTransportBuilder.prototype =
         }
         break;
       default:
        throw Cr.NS_ERROR_ILLEGAL_VALUE;
     }
 
     // TODO bug 1228235 we should have a way to let device providers customize
     // the time-out duration.
-    let timeout;
-    try {
-      timeout = Services.prefs.getIntPref("presentation.receiver.loading.timeout");
-    } catch (e) {
-      // This happens if the pref doesn't exist, so we have a default value.
-      timeout = 10000;
-    }
+    let timeout = Services.prefs.getIntPref("presentation.receiver.loading.timeout", 10000);
 
     // The timer is to check if the negotiation finishes on time.
     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     this._timer.initWithCallback(this, timeout, this._timer.TYPE_ONE_SHOT);
   },
 
   notify: function() {
     if (!this._sessionTransport) {
--- a/dom/presentation/provider/AndroidCastDeviceProvider.js
+++ b/dom/presentation/provider/AndroidCastDeviceProvider.js
@@ -353,22 +353,21 @@ ChromecastRemoteDisplayDevice.prototype 
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice,
                                          Ci.nsIPresentationLocalDevice,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsIObserver]),
 };
 
 function AndroidCastDeviceProvider() {
+  this._listener = null;
+  this._deviceList = new Map();
 }
 
 AndroidCastDeviceProvider.prototype = {
-  _listener: null,
-  _deviceList: new Map(),
-
   onSessionRequest: function APDP_onSessionRequest(aDeviceId,
                                                    aUrl,
                                                    aPresentationId,
                                                    aControlChannel) {
     log("AndroidCastDeviceProvider - onSessionRequest"
         + " aDeviceId=" + aDeviceId);
     let device = this._deviceList.get(aDeviceId);
     let receiverDevice = new ChromecastRemoteDisplayDevice(this,
@@ -398,45 +397,48 @@ AndroidCastDeviceProvider.prototype = {
 
   // nsIPresentationDeviceProvider
   set listener(aListener) {
     this._listener = aListener;
 
     // When unload this provider.
     if (!this._listener) {
       // remove observer
-      Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_ADDED);
-      Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_CHANGED);
-      Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_REMOVED);
+      EventDispatcher.instance.unregisterListener(this, [
+        TOPIC_ANDROID_CAST_DEVICE_ADDED,
+        TOPIC_ANDROID_CAST_DEVICE_CHANGED,
+        TOPIC_ANDROID_CAST_DEVICE_REMOVED,
+      ]);
       return;
     }
 
+    // Observer registration
+    EventDispatcher.instance.registerListener(this, [
+      TOPIC_ANDROID_CAST_DEVICE_ADDED,
+      TOPIC_ANDROID_CAST_DEVICE_CHANGED,
+      TOPIC_ANDROID_CAST_DEVICE_REMOVED,
+    ]);
+
     // Sync all device already found by Android.
     EventDispatcher.instance.sendRequest({ type: TOPIC_ANDROID_CAST_DEVICE_SYNCDEVICE });
-    // Observer registration
-    Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_ADDED, false);
-    Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_CHANGED, false);
-    Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_REMOVED, false);
   },
 
   get listener() {
     return this._listener;
   },
 
   forceDiscovery: function APDP_forceDiscovery() {
     // There is no API to do force discovery in Android SDK.
   },
 
-  // nsIObserver
-  observe: function APDP_observe(aSubject, aTopic, aData) {
-    log('observe ' + aTopic + ': ' + aData);
-    switch (aTopic) {
+  onEvent: function APDP_onEvent(event, data, callback) {
+    switch (event) {
       case TOPIC_ANDROID_CAST_DEVICE_ADDED:
       case TOPIC_ANDROID_CAST_DEVICE_CHANGED: {
-        let deviceInfo = JSON.parse(aData);
+        let deviceInfo = data;
         let deviceId   = deviceInfo.uuid;
 
         if (!this._deviceList.has(deviceId)) {
           let device = new ChromecastRemoteDisplayDevice(this,
                                                          deviceInfo.uuid,
                                                          deviceInfo.friendlyName,
                                                          Ci.nsIPresentationService.ROLE_CONTROLLER);
           this._deviceList.set(device.id, device);
@@ -444,17 +446,17 @@ AndroidCastDeviceProvider.prototype = {
         } else {
           let device = this._deviceList.get(deviceId);
           device.update(deviceInfo.friendlyName);
           this._listener.updateDevice(device);
         }
         break;
       }
       case TOPIC_ANDROID_CAST_DEVICE_REMOVED: {
-        let deviceId = aData;
+        let deviceId = data.id;
         if (!this._deviceList.has(deviceId)) {
           break;
         }
 
         let device   = this._deviceList.get(deviceId);
         this._listener.removeDevice(device);
         this._deviceList.delete(deviceId);
         break;
--- a/dom/system/gonk/DataCallInterfaceService.js
+++ b/dom/system/gonk/DataCallInterfaceService.js
@@ -34,22 +34,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService",
                                    "@mozilla.org/mobileconnection/mobileconnectionservice;1",
                                    "nsIMobileConnectionService");
 
 var DEBUG = RIL.DEBUG_RIL;
 
 function updateDebugFlag() {
   // Read debug setting from pref
-  let debugPref;
-  try {
-    debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED);
-  } catch (e) {
-    debugPref = false;
-  }
+  let debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED, false);
   DEBUG = debugPref || RIL.DEBUG_RIL;
 }
 updateDebugFlag();
 
 function DataCall(aAttributes) {
   for (let key in aAttributes) {
     if (key === "pdpType") {
       // Convert pdp type into constant int value.
--- a/dom/system/gonk/DataCallManager.js
+++ b/dom/system/gonk/DataCallManager.js
@@ -80,22 +80,17 @@ const NETWORK_STATE_DISCONNECTED  = Ci.n
 
 const INT32_MAX = 2147483647;
 
 // set to true in ril_consts.js to see debug messages
 var DEBUG = RIL.DEBUG_RIL;
 
 function updateDebugFlag() {
   // Read debug setting from pref
-  let debugPref;
-  try {
-    debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED);
-  } catch (e) {
-    debugPref = false;
-  }
+  let debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED, false);
   DEBUG = debugPref || RIL.DEBUG_RIL;
 }
 updateDebugFlag();
 
 function DataCallManager() {
   this._connectionHandlers = [];
 
   let numRadioInterfaces = gMobileConnectionService.numItems;
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -63,22 +63,17 @@ const HW_DEFAULT_CLIENT_ID = 0;
 const NETWORK_TYPE_WIFI        = Ci.nsINetworkInfo.NETWORK_TYPE_WIFI;
 const NETWORK_TYPE_MOBILE      = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE;
 
 // set to true in ril_consts.js to see debug messages
 var DEBUG = RIL.DEBUG_RIL;
 
 function updateDebugFlag() {
   // Read debug setting from pref
-  let debugPref;
-  try {
-    debugPref = Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
-  } catch (e) {
-    debugPref = false;
-  }
+  let debugPref = Services.prefs.getBoolPref(kPrefRilDebuggingEnabled, false);
   DEBUG = RIL.DEBUG_RIL || debugPref;
 }
 updateDebugFlag();
 
 function debug(s) {
   dump("-*- RadioInterfaceLayer: " + s + "\n");
 }
 
--- a/dom/tests/mochitest/chrome/window_focus.xul
+++ b/dom/tests/mochitest/chrome/window_focus.xul
@@ -1339,19 +1339,18 @@ function switchWindowTest(otherWindow, f
   fm.activeWindow = topWindow;
   fm.activeWindow = otherWindow;
   is(otherTextbox.selectionStart, 2, "selectionStart after textbox focus and window raise");
   is(otherTextbox.selectionEnd, 3, "selectionEnd after textbox focus and window raise");
   is(fm.getLastFocusMethod(null), fm.FLAG_BYMOVEFOCUS, "last focus method after textbox focus and window raise");
 
   fm.clearFocus(otherWindow);
 
-  // test to ensure that a synthetic event works
-  var synevent = document.createEvent("Event");
-  synevent.initEvent("focus", false, false);
+  // test to ensure that a synthetic event won't move focus
+  var synevent = new FocusEvent("focus", {});
   otherTextbox.inputField.dispatchEvent(synevent);
   is(synevent.type, "focus", "event.type after synthetic focus event");
   is(synevent.target, otherTextbox, "event.target after synthetic focus event");
   is(fm.focusedElement, null, "focusedElement after synthetic focus event");
   is(otherWindow.document.activeElement, otherWindow.document.documentElement,
      "document.activeElement after synthetic focus event");
 
   // check accessing a focus event after the event has finishing firing
--- a/dom/tests/mochitest/localstorage/test_localStorageQuotaPrivateBrowsing_perwindowpb.html
+++ b/dom/tests/mochitest/localstorage/test_localStorageQuotaPrivateBrowsing_perwindowpb.html
@@ -9,23 +9,17 @@
 SimpleTest.waitForExplicitFinish();
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const Ci = Components.interfaces;
 const CONTENT_PAGE = "http://mochi.test:8888/chrome/dom/tests/mochitest/localstorage/page_blank.html";
 const slavePath = "/chrome/dom/tests/mochitest/localstorage/";
 var currentTest = 1;
-var quota;
-
-try {
-  quota = Services.prefs.getIntPref("dom.storage.default_quota");
-} catch (ex) {
-  quota = 5 * 1024;
-}
+var quota = Services.prefs.getIntPref("dom.storage.default_quota", 5 * 1024);
 Services.prefs.setIntPref("browser.startup.page", 0);
 Services.prefs.setIntPref("dom.storage.default_quota", 1);
 
 var slaveLoadsPending = 1;
 var slaveOrigin = "";
 var slave = null;
 var failureRegExp = new RegExp("^FAILURE");
 
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=4 sw=2 et tw=78: */
 /* 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 "EditorEventListener.h"
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc.
+#include "mozilla/ContentEvents.h"      // for InternalFocusEvent
 #include "mozilla/EditorBase.h"         // for EditorBase, etc.
 #include "mozilla/EventListenerManager.h" // for EventListenerManager
 #include "mozilla/IMEStateManager.h"    // for IMEStateManager
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/TextEvents.h"         // for WidgetCompositionEvent
 #include "mozilla/dom/Element.h"        // for Element
 #include "mozilla/dom/Event.h"          // for Event
 #include "mozilla/dom/EventTarget.h"    // for EventTarget
@@ -183,18 +184,21 @@ EditorEventListener::InstallToEditor()
                                NS_LITERAL_STRING("mousedown"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("mouseup"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("click"),
                                TrustedEventsAtCapture());
-// Focus event doesn't bubble so adding the listener to capturing phase.
-// Make sure this works after bug 235441 gets fixed.
+  // Focus event doesn't bubble so adding the listener to capturing phase.
+  // XXX Should we listen focus/blur events of system group too? Or should
+  //     editor notified focus/blur of the element from nsFocusManager
+  //     directly?  Because if the event propagation is stopped by JS,
+  //     editor cannot initialize selection as expected.
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("blur"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("focus"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("text"),
@@ -461,45 +465,37 @@ EditorEventListener::HandleEvent(nsIDOME
         mMouseDownOrUpConsumedByIME = false;
         mouseEvent->AsEvent()->PreventDefault();
         return NS_OK;
       }
       return MouseClick(mouseEvent);
     }
     // focus
     case eFocus:
-      return Focus(internalEvent);
+      return Focus(internalEvent->AsFocusEvent());
     // blur
     case eBlur:
-      return Blur(internalEvent);
+      return Blur(internalEvent->AsFocusEvent());
     // text
     case eCompositionChange:
       return HandleChangeComposition(internalEvent->AsCompositionEvent());
     // compositionstart
     case eCompositionStart:
       return HandleStartComposition(internalEvent->AsCompositionEvent());
     // compositionend
     case eCompositionEnd:
       HandleEndComposition(internalEvent->AsCompositionEvent());
       return NS_OK;
     default:
       break;
   }
 
+#ifdef DEBUG
   nsAutoString eventType;
   aEvent->GetType(eventType);
-  // We should accept "focus" and "blur" event even if it's synthesized with
-  // wrong interface for compatibility with older Gecko.
-  if (eventType.EqualsLiteral("focus")) {
-    return Focus(internalEvent);
-  }
-  if (eventType.EqualsLiteral("blur")) {
-    return Blur(internalEvent);
-  }
-#ifdef DEBUG
   nsPrintfCString assertMessage("Editor doesn't handle \"%s\" event "
     "because its internal event doesn't have proper message",
     NS_ConvertUTF16toUTF8(eventType).get());
   NS_ASSERTION(false, assertMessage.get());
 #endif
 
   return NS_OK;
 }
@@ -1073,28 +1069,22 @@ EditorEventListener::HandleEndCompositio
     return;
   }
   MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
              "eCompositionEnd shouldn't be cancelable");
   editorBase->EndIMEComposition();
 }
 
 nsresult
-EditorEventListener::Focus(WidgetEvent* aFocusEvent)
+EditorEventListener::Focus(InternalFocusEvent* aFocusEvent)
 {
   if (NS_WARN_IF(!aFocusEvent) || DetachedFromEditor()) {
     return NS_OK;
   }
 
-  // XXX If aFocusEvent was created by chrome script, its defaultPrevented
-  //     may be true, though.  We shouldn't handle such event but we don't
-  //     have a way to distinguish if coming event is created by chrome script.
-  NS_WARNING_ASSERTION(!aFocusEvent->DefaultPrevented(),
-                       "eFocus event shouldn't be cancelable");
-
   // Don't turn on selection and caret when the editor is disabled.
   RefPtr<EditorBase> editorBase(mEditorBase);
   if (editorBase->IsDisabled()) {
     return NS_OK;
   }
 
   // Spell check a textarea the first time that it is focused.
   SpellCheckIfNeeded();
@@ -1162,28 +1152,22 @@ EditorEventListener::Focus(WidgetEvent* 
   nsCOMPtr<nsIContent> focusedContent = editorBase->GetFocusedContentForIME();
   IMEStateManager::OnFocusInEditor(ps->GetPresContext(), focusedContent,
                                    editorBase);
 
   return NS_OK;
 }
 
 nsresult
-EditorEventListener::Blur(WidgetEvent* aBlurEvent)
+EditorEventListener::Blur(InternalFocusEvent* aBlurEvent)
 {
   if (NS_WARN_IF(!aBlurEvent) || DetachedFromEditor()) {
     return NS_OK;
   }
 
-  // XXX If aBlurEvent was created by chrome script, its defaultPrevented
-  //     may be true, though.  We shouldn't handle such event but we don't
-  //     have a way to distinguish if coming event is created by chrome script.
-  NS_WARNING_ASSERTION(!aBlurEvent->DefaultPrevented(),
-                       "eBlur event shouldn't be cancelable");
-
   // check if something else is focused. If another element is focused, then
   // we should not change the selection.
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   NS_ENSURE_TRUE(fm, NS_OK);
 
   nsCOMPtr<nsIDOMElement> element;
   fm->GetFocusedElement(getter_AddRefs(element));
   if (!element) {
--- a/editor/libeditor/EditorEventListener.h
+++ b/editor/libeditor/EditorEventListener.h
@@ -63,18 +63,18 @@ protected:
 #endif
   nsresult KeyPress(WidgetKeyboardEvent* aKeyboardEvent);
   nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent);
   nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent);
   void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent);
   virtual nsresult MouseDown(nsIDOMMouseEvent* aMouseEvent);
   virtual nsresult MouseUp(nsIDOMMouseEvent* aMouseEvent) { return NS_OK; }
   virtual nsresult MouseClick(nsIDOMMouseEvent* aMouseEvent);
-  nsresult Focus(WidgetEvent* aFocusEvent);
-  nsresult Blur(WidgetEvent* aBlurEvent);
+  nsresult Focus(InternalFocusEvent* aFocusEvent);
+  nsresult Blur(InternalFocusEvent* aBlurEvent);
   nsresult DragEnter(nsIDOMDragEvent* aDragEvent);
   nsresult DragOver(nsIDOMDragEvent* aDragEvent);
   nsresult DragExit(nsIDOMDragEvent* aDragEvent);
   nsresult Drop(nsIDOMDragEvent* aDragEvent);
 
   bool CanDrop(nsIDOMDragEvent* aEvent);
   void CleanupDragDropCaret();
   already_AddRefed<nsIPresShell> GetPresShell();
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -4,28 +4,80 @@
  * 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 "AnimationHelper.h"
 #include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode
 #include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite
 #include "mozilla/dom/KeyframeEffectReadOnly.h" // for dom::KeyFrameEffectReadOnly
+#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
 #include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction
-#include "mozilla/layers/LayersMessages.h" // for TransformFunction, etc
 #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
 
 namespace mozilla {
 namespace layers {
 
 struct StyleAnimationValueCompositePair {
   StyleAnimationValue mValue;
   dom::CompositeOperation mComposite;
 };
 
+void
+CompositorAnimationStorage::Clear()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+  mAnimatedValues.Clear();
+  mAnimations.Clear();
+
+}
+
+AnimatedValue*
+CompositorAnimationStorage::GetAnimatedValue(const uint64_t& aId) const
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  return mAnimatedValues.Get(aId);
+}
+
+void
+CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
+                                             gfx::Matrix4x4&& aTransformInDevSpace,
+                                             gfx::Matrix4x4&& aFrameTransform,
+                                             const TransformData& aData)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AnimatedValue* value = new AnimatedValue(Move(aTransformInDevSpace), Move(aFrameTransform), aData);
+  mAnimatedValues.Put(aId, value);
+}
+
+void
+CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
+                                             const float& aOpacity)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AnimatedValue* value = new AnimatedValue(aOpacity);
+  mAnimatedValues.Put(aId, value);
+}
+
+AnimationArray*
+CompositorAnimationStorage::GetAnimations(const uint64_t& aId) const
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  return mAnimations.Get(aId);
+}
+
+void
+CompositorAnimationStorage::SetAnimations(uint64_t aId, const AnimationArray& aValue)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AnimationArray* value = new AnimationArray(aValue);
+  mAnimations.Put(aId, value);
+}
+
 static StyleAnimationValue
 SampleValue(float aPortion, const layers::Animation& aAnimation,
             const StyleAnimationValueCompositePair& aStart,
             const StyleAnimationValueCompositePair& aEnd,
             const StyleAnimationValue& aLastValue,
             uint64_t aCurrentIteration,
             const StyleAnimationValue& aUnderlyingValue)
 {
@@ -409,10 +461,22 @@ AnimationHelper::SetAnimations(Animation
     InfallibleTArray<StyleAnimationValue>& endValues = data->mEndValues;
     for (const AnimationSegment& segment : segments) {
       startValues.AppendElement(ToStyleAnimationValue(segment.startState()));
       endValues.AppendElement(ToStyleAnimationValue(segment.endState()));
     }
   }
 }
 
+uint64_t
+AnimationHelper::GetNextCompositorAnimationsId()
+{
+  static uint32_t sNextId = 0;
+  ++sNextId;
+
+  uint32_t procId = static_cast<uint32_t>(base::GetCurrentProcId());
+  uint64_t nextId = procId;
+  nextId = nextId << 32 | sNextId;
+  return nextId;
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -3,45 +3,145 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_AnimationHelper_h
 #define mozilla_layers_AnimationHelper_h
 
 #include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
+#include "mozilla/layers/LayersMessages.h" // for TransformData, etc
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 
 
 namespace mozilla {
-  class StyleAnimationValue;
+class StyleAnimationValue;
 namespace layers {
 class Animation;
 
 typedef InfallibleTArray<layers::Animation> AnimationArray;
 
 struct AnimData {
   InfallibleTArray<mozilla::StyleAnimationValue> mStartValues;
   InfallibleTArray<mozilla::StyleAnimationValue> mEndValues;
   InfallibleTArray<Maybe<mozilla::ComputedTimingFunction>> mFunctions;
 };
 
+struct AnimationTransform {
+  /*
+   * This transform is calculated from sampleanimation in device pixel
+   * and used by compositor.
+   */
+  gfx::Matrix4x4 mTransformInDevSpace;
+  /*
+   * This transform is calculated from frame and used by getOMTAStyle()
+   * for OMTA testing.
+   */
+  gfx::Matrix4x4 mFrameTransform;
+  TransformData mData;
+};
+
+struct AnimatedValue {
+  enum {
+    TRANSFORM,
+    OPACITY,
+    NONE
+  } mType {NONE};
+
+  union {
+    AnimationTransform mTransform;
+    float mOpacity;
+  };
+
+  AnimatedValue(gfx::Matrix4x4&& aTransformInDevSpace,
+                gfx::Matrix4x4&& aFrameTransform,
+                const TransformData& aData)
+    : mType(AnimatedValue::TRANSFORM)
+  {
+    mTransform.mTransformInDevSpace = Move(aTransformInDevSpace);
+    mTransform.mFrameTransform = Move(aFrameTransform);
+    mTransform.mData = aData;
+  }
+
+  explicit AnimatedValue(const float& aValue)
+    : mType(AnimatedValue::OPACITY)
+    , mOpacity(aValue)
+  {
+  }
+
+  ~AnimatedValue() {}
+
+private:
+  AnimatedValue() = delete;
+};
+
+// CompositorAnimationStorage stores the layer animations and animated value
+// after sampling based on an unique id (CompositorAnimationsId)
+class CompositorAnimationStorage final
+{
+  typedef nsClassHashtable<nsUint64HashKey, AnimatedValue> AnimatedValueTable;
+  typedef nsClassHashtable<nsUint64HashKey, AnimationArray> AnimationsTable;
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorAnimationStorage)
+public:
+
+  /**
+   * Set the animation transform based on the unique id
+   */
+  void SetAnimatedValue(uint64_t aId,
+                        gfx::Matrix4x4&& aTransformInDevSpace,
+                        gfx::Matrix4x4&& aFrameTransform,
+                        const TransformData& aData);
+
+  /**
+   * Set the animation opacity based on the unique id
+   */
+  void SetAnimatedValue(uint64_t aId, const float& aOpacity);
+
+  /**
+   * Return the animated value if a given id can map to its animated value
+   */
+  AnimatedValue* GetAnimatedValue(const uint64_t& aId) const;
+
+  /**
+   * Set the animations based on the unique id
+   */
+  void SetAnimations(uint64_t aId, const AnimationArray& aAnimations);
+
+  /**
+   * Return the animations if a given id can map to its animations
+   */
+  AnimationArray* GetAnimations(const uint64_t& aId) const;
+
+  /**
+   * Clear AnimatedValues and Animations data
+   */
+  void Clear();
+
+private:
+  ~CompositorAnimationStorage() { Clear(); };
+
+private:
+  AnimatedValueTable mAnimatedValues;
+  AnimationsTable mAnimations;
+};
+
 class AnimationHelper
 {
 public:
-
   static bool
   SampleAnimationForEachNode(TimeStamp aPoint,
                              AnimationArray& aAnimations,
                              InfallibleTArray<AnimData>& aAnimationData,
                              StyleAnimationValue& aAnimationValue,
                              bool& aHasInEffectAnimations);
 
   static void
   SetAnimations(AnimationArray& aAnimations,
                 InfallibleTArray<AnimData>& aAnimData,
                 StyleAnimationValue& aBaseAnimationStyle);
+  static uint64_t GetNextCompositorAnimationsId();
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_AnimationHelper_h
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -187,32 +187,40 @@ LayerManager::RemoveUserData(void* aKey)
 // Layer
 
 Layer::Layer(LayerManager* aManager, void* aImplData) :
   mManager(aManager),
   mParent(nullptr),
   mNextSibling(nullptr),
   mPrevSibling(nullptr),
   mImplData(aImplData),
+  mCompositorAnimationsId(0),
   mUseTileSourceRect(false),
 #ifdef DEBUG
   mDebugColorIndex(0),
 #endif
   mAnimationGeneration(0)
 {
 }
 
 Layer::~Layer()
 {
 }
 
 Animation*
 Layer::AddAnimation()
 {
-  MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) AddAnimation", this));
+  // Here generates a new id when the first animation is added and
+  // this id is used to represent the animations in this layer.
+  if (!mCompositorAnimationsId) {
+    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
+  }
+
+  MOZ_LAYERS_LOG_IF_SHADOWABLE(
+    this, ("Layer::Mutated(%p) AddAnimation with id=%" PRIu64, this, mCompositorAnimationsId));
 
   MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
 
   Animation* anim = mAnimations.AppendElement();
 
   Mutated();
   return anim;
 }
@@ -223,16 +231,17 @@ Layer::ClearAnimations()
   mPendingAnimations = nullptr;
 
   if (mAnimations.IsEmpty() && mAnimationData.IsEmpty()) {
     return;
   }
 
   MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ClearAnimations", this));
   mAnimations.Clear();
+  mCompositorAnimationsId = 0;
   mAnimationData.Clear();
   Mutated();
 }
 
 Animation*
 Layer::AddAnimationForNextTransaction()
 {
   MOZ_ASSERT(mPendingAnimations,
@@ -250,21 +259,23 @@ Layer::ClearAnimationsForNextTransaction
   if (!mPendingAnimations) {
     mPendingAnimations = new AnimationArray;
   }
 
   mPendingAnimations->Clear();
 }
 
 void
-Layer::SetAnimations(const AnimationArray& aAnimations)
+Layer::SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations)
 {
-  MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) SetAnimations", this));
+  MOZ_LAYERS_LOG_IF_SHADOWABLE(
+    this, ("Layer::Mutated(%p) SetCompositorAnimations with id=%" PRIu64, this, mCompositorAnimationsId));
 
-  mAnimations = aAnimations;
+  mAnimations = aCompositorAnimations.animations();
+  mCompositorAnimationsId = aCompositorAnimations.id();
   mAnimationData.Clear();
   AnimationHelper::SetAnimations(mAnimations,
                                  mAnimationData,
                                  mBaseAnimationStyle);
 
   Mutated();
 }
 
@@ -1910,17 +1921,19 @@ Layer::PrintInfo(std::stringstream& aStr
   }
   for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) {
     if (!mScrollMetadata[i].IsDefault()) {
       aStream << nsPrintfCString(" [metrics%d=", i).get();
       AppendToString(aStream, mScrollMetadata[i], "", "]");
     }
   }
   if (!mAnimations.IsEmpty()) {
-    aStream << nsPrintfCString(" [%d animations]", (int) mAnimations.Length()).get();
+    aStream << nsPrintfCString(" [%d animations with id=%" PRIu64 " ]",
+                               (int) mAnimations.Length(),
+                               mCompositorAnimationsId).get();
   }
 }
 
 // The static helper function sets the transform matrix into the packet
 static void
 DumpTransform(layerscope::LayersPacket::Layer::Matrix* aLayerMatrix, const Matrix4x4& aMatrix)
 {
   aLayerMatrix->set_is2d(aMatrix.Is2D());
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -83,16 +83,17 @@ class ClientLayerManager;
 class HostLayerManager;
 class Layer;
 class LayerMetricsWrapper;
 class PaintedLayer;
 class ContainerLayer;
 class ImageLayer;
 class DisplayItemLayer;
 class ColorLayer;
+class CompositorAnimations;
 class CompositorBridgeChild;
 class TextLayer;
 class CanvasLayer;
 class BorderLayer;
 class ReadbackLayer;
 class ReadbackProcessor;
 class RefLayer;
 class HostLayer;
@@ -1218,17 +1219,17 @@ public:
   // Call AddAnimation to add a new animation to this layer from layout code.
   // Caller must fill in all the properties of the returned animation.
   // A later animation overrides an earlier one.
   Animation* AddAnimation();
   // ClearAnimations clears animations on this layer.
   void ClearAnimations();
   // This is only called when the layer tree is updated. Do not call this from
   // layout code.  To add an animation to this layer, use AddAnimation.
-  void SetAnimations(const AnimationArray& aAnimations);
+  void SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations);
   // Go through all animations in this layer and its children and, for
   // any animations with a null start time, update their start time such
   // that at |aReadyTime| the animation's current time corresponds to its
   // 'initial current time' value.
   void StartPendingAnimations(const TimeStamp& aReadyTime);
 
   // These are a parallel to AddAnimation and clearAnimations, except
   // they add pending animations that apply only when the next
@@ -1410,16 +1411,17 @@ public:
    *  traversal.
    */
   bool GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult,
                                            nsIntPoint* aLayerOffset);
 
   // Note that all lengths in animation data are either in CSS pixels or app
   // units and must be converted to device pixels by the compositor.
   AnimationArray& GetAnimations() { return mAnimations; }
+  uint64_t GetCompositorAnimationsId() { return mCompositorAnimationsId; }
   InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
 
   uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
   void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
 
   bool HasTransformAnimation() const;
 
   StyleAnimationValue GetBaseAnimationStyle() const
@@ -1916,16 +1918,17 @@ protected:
   nsTArray<ScrollMetadata> mScrollMetadata;
   EventRegions mEventRegions;
   // A mutation of |mTransform| that we've queued to be applied at the
   // end of the next transaction (if nothing else overrides it in the
   // meantime).
   nsAutoPtr<gfx::Matrix4x4> mPendingTransform;
   gfx::Matrix4x4 mEffectiveTransform;
   AnimationArray mAnimations;
+  uint64_t mCompositorAnimationsId;
   // See mPendingTransform above.
   nsAutoPtr<AnimationArray> mPendingAnimations;
   InfallibleTArray<AnimData> mAnimationData;
   Maybe<ParentLayerIntRect> mClipRect;
   gfx::IntRect mTileSourceRect;
   gfx::TiledIntRegion mInvalidRegion;
   nsTArray<RefPtr<AsyncPanZoomController> > mApzcs;
   bool mUseTileSourceRect;
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -62,22 +62,24 @@ IsSameDimension(dom::ScreenOrientationIn
 }
 
 static bool
 ContentMightReflowOnOrientationChange(const IntRect& rect)
 {
   return rect.width != rect.height;
 }
 
-AsyncCompositionManager::AsyncCompositionManager(HostLayerManager* aManager)
+  AsyncCompositionManager::AsyncCompositionManager(CompositorBridgeParent* aParent,
+                                                   HostLayerManager* aManager)
   : mLayerManager(aManager)
   , mIsFirstPaint(true)
   , mLayersUpdated(false)
   , mPaintSyncId(0)
   , mReadyForCompose(true)
+  , mCompositorBridge(aParent)
 {
 }
 
 AsyncCompositionManager::~AsyncCompositionManager()
 {
 }
 
 void
@@ -571,101 +573,116 @@ AsyncCompositionManager::AlignFixedAndSt
     }
   }
 
   return;
 }
 
 static void
 ApplyAnimatedValue(Layer* aLayer,
+                   CompositorAnimationStorage* aStorage,
                    nsCSSPropertyID aProperty,
                    const AnimationData& aAnimationData,
                    const StyleAnimationValue& aValue)
 {
   if (aValue.IsNull()) {
     // Return gracefully if we have no valid StyleAnimationValue.
     return;
   }
 
   HostLayer* layerCompositor = aLayer->AsHostLayer();
   switch (aProperty) {
     case eCSSProperty_opacity: {
       MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Float,
                  "Interpolated value for opacity should be float");
       layerCompositor->SetShadowOpacity(aValue.GetFloatValue());
       layerCompositor->SetShadowOpacitySetByAnimation(true);
+      aStorage->SetAnimatedValue(aLayer->GetCompositorAnimationsId(),
+                                 aValue.GetFloatValue());
+
       break;
     }
     case eCSSProperty_transform: {
       MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Transform,
                  "The unit of interpolated value for transform should be "
                  "transform");
       nsCSSValueSharedList* list = aValue.GetCSSValueSharedListValue();
 
       const TransformData& transformData = aAnimationData.get_TransformData();
       nsPoint origin = transformData.origin();
       // we expect all our transform data to arrive in device pixels
       Point3D transformOrigin = transformData.transformOrigin();
       nsDisplayTransform::FrameTransformProperties props(list,
                                                          transformOrigin);
 
+      Matrix4x4 transform =
+        nsDisplayTransform::GetResultingTransformMatrix(props, origin,
+                                                        transformData.appUnitsPerDevPixel(),
+                                                        0, &transformData.bounds());
+      Matrix4x4 frameTransform = transform;
+
       // If our parent layer is a perspective layer, then the offset into reference
       // frame coordinates is already on that layer. If not, then we need to ask
       // for it to be added here.
-      uint32_t flags = 0;
       if (!aLayer->GetParent() ||
           !aLayer->GetParent()->GetTransformIsPerspective()) {
-        flags = nsDisplayTransform::OFFSET_BY_ORIGIN;
+        nsLayoutUtils::PostTranslate(transform, origin,
+                                     transformData.appUnitsPerDevPixel(),
+                                     true);
       }
 
-      Matrix4x4 transform =
-        nsDisplayTransform::GetResultingTransformMatrix(props, origin,
-                                                        transformData.appUnitsPerDevPixel(),
-                                                        flags, &transformData.bounds());
-
       if (ContainerLayer* c = aLayer->AsContainerLayer()) {
         transform.PostScale(c->GetInheritedXScale(), c->GetInheritedYScale(), 1);
       }
+
       layerCompositor->SetShadowBaseTransform(transform);
       layerCompositor->SetShadowTransformSetByAnimation(true);
+      aStorage->SetAnimatedValue(aLayer->GetCompositorAnimationsId(),
+                                 Move(transform), Move(frameTransform),
+                                 transformData);
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
   }
 }
 
 static bool
-SampleAnimations(Layer* aLayer, TimeStamp aPoint, uint64_t* aLayerAreaAnimated)
+SampleAnimations(Layer* aLayer,
+                 CompositorAnimationStorage* aStorage,
+                 TimeStamp aPoint,
+                 uint64_t* aLayerAreaAnimated)
 {
   bool activeAnimations = false;
 
   ForEachNode<ForwardIterator>(
       aLayer,
-      [&activeAnimations, &aPoint, &aLayerAreaAnimated] (Layer* layer)
+      [aStorage, &activeAnimations, &aPoint, &aLayerAreaAnimated] (Layer* layer)
       {
         bool hasInEffectAnimations = false;
         StyleAnimationValue animationValue = layer->GetBaseAnimationStyle();
         activeAnimations |=
           AnimationHelper::SampleAnimationForEachNode(aPoint,
                                                       layer->GetAnimations(),
                                                       layer->GetAnimationData(),
                                                       animationValue,
                                                       hasInEffectAnimations);
         if (hasInEffectAnimations) {
           Animation& animation = layer->GetAnimations().LastElement();
           ApplyAnimatedValue(layer,
+                             aStorage,
                              animation.property(),
                              animation.data(),
                              animationValue);
           if (aLayerAreaAnimated) {
             *aLayerAreaAnimated += (layer->GetVisibleRegion().Area());
           }
         }
       });
+
   return activeAnimations;
 }
 
 static bool
 SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aSampleTime)
 {
   bool activeAnimations = false;
 
@@ -1303,31 +1320,43 @@ AsyncCompositionManager::TransformShadow
   PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree",
     js::ProfileEntry::Category::GRAPHICS);
 
   Layer* root = mLayerManager->GetRoot();
   if (!root) {
     return false;
   }
 
+  // GetAnimationStorage in CompositorBridgeParent expects id as 0
+  CompositorAnimationStorage* storage =
+    mCompositorBridge->GetAnimationStorage(0);
   // First, compute and set the shadow transforms from OMT animations.
   // NB: we must sample animations *before* sampling pan/zoom
   // transforms.
   // Use a previous vsync time to make main thread animations and compositor
   // more in sync with each other.
   // On the initial frame we use aVsyncTimestamp here so the timestamp on the
   // second frame are the same as the initial frame, but it does not matter.
   uint64_t layerAreaAnimated = 0;
-  bool wantNextFrame = SampleAnimations(root,
-    !mPreviousFrameTimeStamp.IsNull() ?
-      mPreviousFrameTimeStamp : aCurrentFrame,
-    &layerAreaAnimated);
+  bool wantNextFrame =
+    SampleAnimations(root,
+                     storage,
+                     !mPreviousFrameTimeStamp.IsNull() ?
+                       mPreviousFrameTimeStamp : aCurrentFrame,
+                     &layerAreaAnimated);
+
   mAnimationMetricsTracker.UpdateAnimationInProgress(
     wantNextFrame, layerAreaAnimated);
 
+  if (!wantNextFrame) {
+    // Clean up the CompositorAnimationStorage because
+    // there are no active animations running
+    storage->Clear();
+  }
+
   // Reset the previous time stamp if we don't already have any running
   // animations to avoid using the time which is far behind for newly
   // started animations.
   mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame : TimeStamp();
 
   if (!(aSkip & TransformsToSkip::APZ)) {
     // FIXME/bug 775437: unify this interface with the ~native-fennec
     // derived code
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -66,17 +66,17 @@ struct AsyncTransform {
 class AsyncCompositionManager final
 {
   friend class AutoResolveRefLayers;
   ~AsyncCompositionManager();
 
 public:
   NS_INLINE_DECL_REFCOUNTING(AsyncCompositionManager)
 
-  explicit AsyncCompositionManager(HostLayerManager* aManager);
+  explicit AsyncCompositionManager(CompositorBridgeParent* aParent, HostLayerManager* aManager);
 
   /**
    * This forces the is-first-paint flag to true. This is intended to
    * be called by the widget code when it loses its viewport information
    * (or for whatever reason wants to refresh the viewport information).
    * The information refresh happens because the compositor will call
    * SetFirstPaintViewport on the next frame of composition.
    */
@@ -236,16 +236,18 @@ private:
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
   LayerTransformRecorder mLayerTransformRecorder;
 
   TimeStamp mPreviousFrameTimeStamp;
   AnimationMetricsTracker mAnimationMetricsTracker;
 
+  CompositorBridgeParent* mCompositorBridge;
+
 #ifdef MOZ_WIDGET_ANDROID
   // The following two fields are only needed on Fennec with C++ APZ, because
   // then we need to reposition the gecko scrollbar to deal with the
   // dynamic toolbar shifting content around.
   FrameMetrics::ViewID mRootScrollableId;
   ScreenMargin mFixedLayerMargins;
 #endif
 };
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -30,16 +30,17 @@
 #include "mozilla/gfx/2D.h"          // for DrawTarget
 #include "mozilla/gfx/GPUChild.h"       // for GfxPrefValue
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/Rect.h"          // for IntSize
 #include "mozilla/gfx/gfxVars.h"        // for gfxVars
 #include "VRManager.h"                  // for VRManager
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"  // for BasicCompositor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
 #include "mozilla/layers/CompositorThread.h"
@@ -315,16 +316,17 @@ CompositorBridgeParent::CompositorBridge
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
   , mResetCompositorMonitor("ResetCompositorMonitor")
   , mRootLayerTreeID(0)
   , mOverrideComposeReadiness(false)
   , mForceCompositionTask(nullptr)
   , mCompositorThreadHolder(CompositorThreadHolder::GetSingleton())
   , mCompositorScheduler(nullptr)
+  , mAnimationStorage(nullptr)
   , mPaintTime(TimeDuration::Forever())
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   , mLastPluginUpdateLayerTreeId(0)
   , mDeferPluginWindows(false)
   , mPluginWindowsHidden(false)
 #endif
 {
   // Always run destructor on the main thread
@@ -634,16 +636,17 @@ CompositorBridgeParent::RecvNotifyApprox
 void
 CompositorBridgeParent::ActorDestroy(ActorDestroyReason why)
 {
   StopAndClearResources();
 
   RemoveCompositor(mCompositorID);
 
   mCompositionManager = nullptr;
+  mAnimationStorage = nullptr;
 
   if (mApzcTreeManager) {
     mApzcTreeManager->ClearTree();
     mApzcTreeManager = nullptr;
   }
 
   { // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
@@ -1294,16 +1297,27 @@ CompositorBridgeParent::ApplyAsyncProper
       CancelCurrentCompositeTask();
       // Pretend we composited in case someone is waiting for this event.
       TimeStamp now = TimeStamp::Now();
       DidComposite(now, now);
     }
   }
 }
 
+CompositorAnimationStorage*
+CompositorBridgeParent::GetAnimationStorage(const uint64_t& aId)
+{
+  MOZ_ASSERT(aId == 0);
+
+  if (!mAnimationStorage) {
+    mAnimationStorage = new CompositorAnimationStorage();
+  }
+  return mAnimationStorage;
+}
+
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvGetFrameUniformity(FrameUniformityData* aOutData)
 {
   mCompositionManager->GetFrameUniformity(aOutData);
   return IPC_OK();
 }
 
 void
@@ -1452,17 +1466,17 @@ CompositorBridgeParent::AllocPLayerTrans
   if (!mLayerManager) {
     NS_WARNING("Failed to initialise Compositor");
     *aSuccess = false;
     LayerTransactionParent* p = new LayerTransactionParent(nullptr, this, 0);
     p->AddIPDLReference();
     return p;
   }
 
-  mCompositionManager = new AsyncCompositionManager(mLayerManager);
+  mCompositionManager = new AsyncCompositionManager(this, mLayerManager);
   *aSuccess = true;
 
   *aTextureFactoryIdentifier = mLayerManager->GetTextureFactoryIdentifier();
   LayerTransactionParent* p = new LayerTransactionParent(mLayerManager, this, 0);
   p->AddIPDLReference();
   return p;
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -59,16 +59,17 @@ class Shmem;
 } // namespace ipc
 
 namespace layers {
 
 class APZCTreeManager;
 class APZCTreeManagerParent;
 class AsyncCompositionManager;
 class Compositor;
+class CompositorAnimationStorage;
 class CompositorBridgeParent;
 class CompositorVsyncScheduler;
 class HostLayerManager;
 class LayerTransactionParent;
 class PAPZParent;
 class CrossProcessCompositorBridgeParent;
 class CompositorThreadHolder;
 class InProcessCompositorSession;
@@ -100,16 +101,17 @@ public:
 
   virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) { }
 
   virtual void ForceComposite(LayerTransactionParent* aLayerTree) { }
   virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
                                  const TimeStamp& aTime) { return true; }
   virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) { }
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree) = 0;
+  virtual CompositorAnimationStorage* GetAnimationStorage(const uint64_t& aId) { return nullptr; }
   virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) = 0;
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) { }
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) = 0;
   virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) {}
 
@@ -222,16 +224,17 @@ public:
                                    const TransactionInfo& aInfo,
                                    bool aHitTestUpdate) override;
   virtual void ForceComposite(LayerTransactionParent* aLayerTree) override;
   virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
                                  const TimeStamp& aTime) override;
   virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) override;
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree)
                override;
+  virtual CompositorAnimationStorage* GetAnimationStorage(const uint64_t& aId) override;
   virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) override;
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) override;
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) override;
   virtual AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aLayerTree) override { return mCompositionManager; }
 
@@ -593,16 +596,17 @@ protected:
   RefPtr<APZCTreeManager> mApzcTreeManager;
 
   RefPtr<CompositorThreadHolder> mCompositorThreadHolder;
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
   // This makes sure the compositorParent is not destroyed before receiving
   // confirmation that the channel is closed.
   // mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy.
   RefPtr<CompositorBridgeParent> mSelfRef;
+  RefPtr<CompositorAnimationStorage> mAnimationStorage;
 
   TimeDuration mPaintTime;
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   // cached plugin data used to reduce the number of updates we request.
   uint64_t mLastPluginUpdateLayerTreeId;
   nsIntPoint mPluginsLayerOffset;
   nsIntRegion mPluginsLayerVisibleRegion;
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/layers/CrossProcessCompositorBridgeParent.h"
 #include <stdint.h>                     // for uint64_t
 #include "LayerTransactionParent.h"     // for LayerTransactionParent
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for CancelableTask, etc
 #include "base/thread.h"                // for Thread
 #include "mozilla/ipc/Transport.h"      // for Transport
+#include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
@@ -403,16 +404,32 @@ CrossProcessCompositorBridgeParent::Appl
   if (!state) {
     return;
   }
 
   MOZ_ASSERT(state->mParent);
   state->mParent->ApplyAsyncProperties(aLayerTree);
 }
 
+CompositorAnimationStorage*
+CrossProcessCompositorBridgeParent::GetAnimationStorage(
+    const uint64_t& aId)
+{
+  MOZ_ASSERT(aId != 0);
+  const CompositorBridgeParent::LayerTreeState* state =
+    CompositorBridgeParent::GetIndirectShadowTree(aId);
+  if (!state) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(state->mParent);
+  // GetAnimationStorage in CompositorBridgeParent expects id as 0
+  return state->mParent->GetAnimationStorage(0);
+}
+
 void
 CrossProcessCompositorBridgeParent::FlushApzRepaints(const LayerTransactionParent* aLayerTree)
 {
   uint64_t id = aLayerTree->GetId();
   MOZ_ASSERT(id != 0);
   const CompositorBridgeParent::LayerTreeState* state =
     CompositorBridgeParent::GetIndirectShadowTree(id);
   if (!state) {
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 
 namespace mozilla {
 namespace layers {
 
 class CompositorOptions;
+class CompositorAnimationStorage;
 
 /**
  * This class handles layer updates pushed directly from child processes to
  * the compositor thread. It's associated with a CompositorBridgeParent on the
  * compositor thread. While it uses the PCompositorBridge protocol to manage
  * these updates, it doesn't actually drive compositing itself. For that it
  * hands off work to the CompositorBridgeParent it's associated with.
  */
@@ -100,16 +101,18 @@ public:
                                    bool aHitTestUpdate) override;
   virtual void ForceComposite(LayerTransactionParent* aLayerTree) override;
   virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) override;
   virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
                                  const TimeStamp& aTime) override;
   virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) override;
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree)
                override;
+  virtual CompositorAnimationStorage*
+    GetAnimationStorage(const uint64_t& aId) override;
   virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) override;
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) override;
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) override;
 
   virtual AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aParent) override;
--- a/gfx/layers/ipc/LayerAnimationUtils.cpp
+++ b/gfx/layers/ipc/LayerAnimationUtils.cpp
@@ -28,18 +28,25 @@ AnimationUtils::TimingFunctionToComputed
       StepFunction sf = aTimingFunction.get_StepFunction();
       nsTimingFunction::Type type = sf.type() == 1 ?
         nsTimingFunction::Type::StepStart :
         nsTimingFunction::Type::StepEnd;
       ComputedTimingFunction result;
       result.Init(nsTimingFunction(type, sf.steps()));
       return Some(result);
     }
+    case TimingFunction::TFramesFunction: {
+      FramesFunction ff = aTimingFunction.get_FramesFunction();
+      ComputedTimingFunction result;
+      result.Init(nsTimingFunction(nsTimingFunction::Type::Frames,
+                                   ff.frames()));
+      return Some(result);
+    }
     default:
       MOZ_ASSERT_UNREACHABLE(
-        "Function must be null, bezier or step");
+        "Function must be null, bezier, step or frames");
       break;
   }
   return Nothing();
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -10,16 +10,17 @@
 #include "apz/src/AsyncPanZoomController.h"
 #include "CompositableHost.h"           // for CompositableParent, Get, etc
 #include "ImageLayers.h"                // for ImageLayer
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "CompositableTransactionParent.h"  // for EditReplyVector
 #include "CompositorBridgeParent.h"
 #include "gfxPrefs.h"
 #include "mozilla/gfx/BasePoint3D.h"    // for BasePoint3D
+#include "mozilla/layers/AnimationHelper.h" // for GetAnimatedPropValue
 #include "mozilla/layers/CanvasLayerComposite.h"
 #include "mozilla/layers/ColorLayerComposite.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/ContainerLayerComposite.h"
 #include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent
 #include "mozilla/layers/ImageLayerComposite.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
@@ -523,17 +524,17 @@ LayerTransactionParent::SetLayerAttribut
   layer->SetVisibleRegion(common.visibleRegion());
   layer->SetEventRegions(common.eventRegions());
   layer->SetClipRect(common.useClipRect() ? Some(common.clipRect()) : Nothing());
   if (LayerHandle maskLayer = common.maskLayer()) {
     layer->SetMaskLayer(AsLayer(maskLayer));
   } else {
     layer->SetMaskLayer(nullptr);
   }
-  layer->SetAnimations(common.animations());
+  layer->SetCompositorAnimations(common.compositorAnimations());
   layer->SetScrollMetadata(common.scrollMetadata());
   layer->SetDisplayListLog(common.displayListLog().get());
 
   // The updated invalid region is added to the existing one, since we can
   // update multiple times before the next composite.
   layer->AddInvalidRegion(common.invalidRegion());
 
   nsTArray<RefPtr<Layer>> maskLayers;
@@ -690,104 +691,77 @@ LayerTransactionParent::RecvSetTestSampl
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvLeaveTestMode()
 {
   mCompositorBridge->LeaveTestMode(this);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-LayerTransactionParent::RecvGetAnimationOpacity(const LayerHandle& aParent,
+LayerTransactionParent::RecvGetAnimationOpacity(const uint64_t& aCompositorAnimationsId,
                                                 float* aOpacity,
                                                 bool* aHasAnimationOpacity)
 {
   *aHasAnimationOpacity = false;
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  RefPtr<Layer> layer = AsLayer(aParent);
-  if (!layer) {
+  mCompositorBridge->ApplyAsyncProperties(this);
+
+  CompositorAnimationStorage* storage =
+    mCompositorBridge->GetAnimationStorage(GetId());
+
+  if (!storage) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  mCompositorBridge->ApplyAsyncProperties(this);
+  auto value = storage->GetAnimatedValue(aCompositorAnimationsId);
 
-  if (!layer->AsHostLayer()->GetShadowOpacitySetByAnimation()) {
+  if (!value || value->mType != AnimatedValue::OPACITY) {
     return IPC_OK();
   }
 
-  *aOpacity = layer->GetLocalOpacity();
+  *aOpacity = value->mOpacity;
   *aHasAnimationOpacity = true;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-LayerTransactionParent::RecvGetAnimationTransform(const LayerHandle& aLayerHandle,
+LayerTransactionParent::RecvGetAnimationTransform(const uint64_t& aCompositorAnimationsId,
                                                   MaybeTransform* aTransform)
 {
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  Layer* layer = AsLayer(aLayerHandle);
-  if (!layer) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-
   // Make sure we apply the latest animation style or else we can end up with
   // a race between when we temporarily clear the animation transform (in
   // CompositorBridgeParent::SetShadowProperties) and when animation recalculates
   // the value.
   mCompositorBridge->ApplyAsyncProperties(this);
 
-  // This method is specific to transforms applied by animation.
-  // This is because this method uses the information stored with an animation
-  // such as the origin of the reference frame corresponding to the layer, to
-  // recover the untranslated transform from the shadow transform. For
-  // transforms that are not set by animation we don't have this information
-  // available.
-  if (!layer->AsHostLayer()->GetShadowTransformSetByAnimation()) {
+  CompositorAnimationStorage* storage =
+    mCompositorBridge->GetAnimationStorage(GetId());
+
+  if (!storage) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  auto value = storage->GetAnimatedValue(aCompositorAnimationsId);
+
+  if (!value || value->mType != AnimatedValue::TRANSFORM) {
     *aTransform = mozilla::void_t();
     return IPC_OK();
   }
 
-  // The following code recovers the untranslated transform
-  // from the shadow transform by undoing the translations in
-  // AsyncCompositionManager::SampleValue.
-
-  Matrix4x4 transform = layer->AsHostLayer()->GetShadowBaseTransform();
-  if (ContainerLayer* c = layer->AsContainerLayer()) {
-    // Undo the scale transform applied by AsyncCompositionManager::SampleValue
-    transform.PostScale(1.0f/c->GetInheritedXScale(),
-                        1.0f/c->GetInheritedYScale(),
-                        1.0f);
-  }
-  float scale = 1;
-  Point3D scaledOrigin;
-  Point3D transformOrigin;
-  for (uint32_t i=0; i < layer->GetAnimations().Length(); i++) {
-    if (layer->GetAnimations()[i].data().type() == AnimationData::TTransformData) {
-      const TransformData& data = layer->GetAnimations()[i].data().get_TransformData();
-      scale = data.appUnitsPerDevPixel();
-      scaledOrigin =
-        Point3D(NS_round(NSAppUnitsToFloatPixels(data.origin().x, scale)),
-                NS_round(NSAppUnitsToFloatPixels(data.origin().y, scale)),
-                0.0f);
-      transformOrigin = data.transformOrigin();
-      break;
-    }
-  }
-
-  // If our parent isn't a perspective layer, then the offset into reference
-  // frame coordinates will have been applied to us. Add an inverse translation
-  // to cancel it out.
-  if (!layer->GetParent() || !layer->GetParent()->GetTransformIsPerspective()) {
-    transform.PostTranslate(-scaledOrigin.x, -scaledOrigin.y, -scaledOrigin.z);
-  }
+  Matrix4x4 transform = value->mTransform.mFrameTransform;
+  const TransformData& data = value->mTransform.mData;
+  float scale = data.appUnitsPerDevPixel();
+  Point3D transformOrigin = data.transformOrigin();
 
   // Undo the rebasing applied by
   // nsDisplayTransform::GetResultingTransformMatrixInternal
   transform.ChangeBasis(-transformOrigin);
 
   // Convert to CSS pixels (this undoes the operations performed by
   // nsStyleTransformMatrix::ProcessTranslatePart which is called from
   // nsDisplayTransform::GetResultingTransformMatrix)
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -120,20 +120,20 @@ protected:
                                                       const TextureInfo& aInfo) override;
   virtual mozilla::ipc::IPCResult RecvReleaseLayer(const LayerHandle& aHandle) override;
   virtual mozilla::ipc::IPCResult RecvReleaseCompositable(const CompositableHandle& aHandle) override;
 
   virtual mozilla::ipc::IPCResult RecvClearCachedResources() override;
   virtual mozilla::ipc::IPCResult RecvForceComposite() override;
   virtual mozilla::ipc::IPCResult RecvSetTestSampleTime(const TimeStamp& aTime) override;
   virtual mozilla::ipc::IPCResult RecvLeaveTestMode() override;
-  virtual mozilla::ipc::IPCResult RecvGetAnimationOpacity(const LayerHandle& aLayerHandle,
+  virtual mozilla::ipc::IPCResult RecvGetAnimationOpacity(const uint64_t& aCompositorAnimationsId,
                                                           float* aOpacity,
                                                           bool* aHasAnimationOpacity) override;
-  virtual mozilla::ipc::IPCResult RecvGetAnimationTransform(const LayerHandle& aLayerHandle,
+  virtual mozilla::ipc::IPCResult RecvGetAnimationTransform(const uint64_t& aCompositorAnimationsId,
                                                             MaybeTransform* aTransform)
                                          override;
   virtual mozilla::ipc::IPCResult RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aId,
                                                            const float& aX, const float& aY) override;
   virtual mozilla::ipc::IPCResult RecvSetAsyncZoom(const FrameMetrics::ViewID& aId,
                                                    const float& aValue) override;
   virtual mozilla::ipc::IPCResult RecvFlushApzRepaints() override;
   virtual mozilla::ipc::IPCResult RecvGetAPZTestData(APZTestData* aOutData) override;
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -97,20 +97,25 @@ struct CubicBezierFunction {
 };
 
 struct StepFunction {
   int steps;
   // 1 = nsTimingFunction::StepStart, 2 = nsTimingFunction::StepEnd
   int type;
 };
 
+struct FramesFunction {
+  int frames;
+};
+
 union TimingFunction {
   null_t;
   CubicBezierFunction;
   StepFunction;
+  FramesFunction;
 };
 
 // Send the angle with units rather than sending all angles in radians
 // to avoid having floating point error introduced by unit switching.
 struct CSSAngle {
   float value;
   int unit; // an nsCSSUnit that is valid for angles
 };
@@ -228,26 +233,33 @@ struct Animation {
   bool isNotPlaying;
   // The base style that animations should composite with. This is only set for
   // animations with a composite mode of additive or accumulate, and only for
   // the first animation in the set (i.e. the animation that is lowest in the
   // stack). In all other cases the value is null_t.
   Animatable baseStyle;
 };
 
+struct CompositorAnimations {
+  Animation[] animations;
+  // This id is used to map the layer animations between content
+  // and compositor side
+  uint64_t id;
+};
+
 // Change a layer's attributes
 struct CommonLayerAttributes {
   LayerIntRegion visibleRegion;
   EventRegions eventRegions;
   bool useClipRect;
   ParentLayerIntRect clipRect;
   LayerHandle maskLayer;
   LayerHandle[] ancestorMaskLayers;
   // Animated colors will only honored for ColorLayers.
-  Animation[] animations;
+  CompositorAnimations compositorAnimations;
   nsIntRegion invalidRegion;
   ScrollMetadata[] scrollMetadata;
   nsCString displayListLog;
 };
 
 struct PaintedLayerAttributes {
   nsIntRegion validRegion;
 };
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -76,25 +76,25 @@ parent:
   // animations. sampleTime must not be null.
   sync SetTestSampleTime(TimeStamp sampleTime);
   // Leave test mode and resume normal compositing
   sync LeaveTestMode();
 
   // Returns the value of the opacity applied to the layer by animation.
   // |hasAnimationOpacity| is true if the layer has an opacity value
   // specified by animation. If it's false, |opacity| value is indefinite.
-  sync GetAnimationOpacity(LayerHandle layer) returns (float opacity,
+  sync GetAnimationOpacity(uint64_t aCompositorAnimationsId) returns (float opacity,
                                                   bool hasAnimationOpacity);
 
   // Returns the value of the transform applied to the layer by animation after
   // factoring out translation components introduced to account for the offset
   // of the corresponding frame and transform origin and after converting to CSS
   // pixels. If the layer is not transformed by animation, the return value will
   // be void_t.
-  sync GetAnimationTransform(LayerHandle layer) returns (MaybeTransform transform);
+  sync GetAnimationTransform(uint64_t aCompositorAnimationId) returns (MaybeTransform transform);
 
   // The next time the layer tree is composited, add this async scroll offset in
   // CSS pixels for the given ViewID.
   // Useful for testing rendering of async scrolling.
   sync SetAsyncScrollOffset(ViewID id, float x, float y);
 
   // The next time the layer tree is composited, include this async zoom in
   // for the given ViewID.
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -663,17 +663,18 @@ ShadowLayerForwarder::EndTransaction(con
     common.useClipRect() = !!mutant->GetClipRect();
     common.clipRect() = (common.useClipRect() ?
                          *mutant->GetClipRect() : ParentLayerIntRect());
     if (Layer* maskLayer = mutant->GetMaskLayer()) {
       common.maskLayer() = Shadow(maskLayer->AsShadowableLayer());
     } else {
       common.maskLayer() = LayerHandle();
     }
-    common.animations() = mutant->GetAnimations();
+    common.compositorAnimations().id() = mutant->GetCompositorAnimationsId();
+    common.compositorAnimations().animations() = mutant->GetAnimations();
     common.invalidRegion() = mutant->GetInvalidRegion().GetRegion();
     common.scrollMetadata() = mutant->GetAllScrollMetadata();
     for (size_t i = 0; i < mutant->GetAncestorMaskLayerCount(); i++) {
       auto layer = Shadow(mutant->GetAncestorMaskLayerAt(i)->AsShadowableLayer());
       common.ancestorMaskLayers().AppendElement(layer);
     }
     nsCString log;
     mutant->GetDisplayListLog(log);
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -81,29 +81,31 @@ class JS_PUBLIC_API(JSTracer)
         // Traversing children is the responsibility of the callback.
         Callback
     };
     bool isMarkingTracer() const { return tag_ == TracerKindTag::Marking || tag_ == TracerKindTag::WeakMarking; }
     bool isWeakMarkingTracer() const { return tag_ == TracerKindTag::WeakMarking; }
     bool isTenuringTracer() const { return tag_ == TracerKindTag::Tenuring; }
     bool isCallbackTracer() const { return tag_ == TracerKindTag::Callback; }
     inline JS::CallbackTracer* asCallbackTracer();
+    bool traceWeakEdges() const { return traceWeakEdges_; }
 #ifdef DEBUG
     bool checkEdges() { return checkEdges_; }
 #endif
 
   protected:
     JSTracer(JSRuntime* rt, TracerKindTag tag,
              WeakMapTraceKind weakTraceKind = TraceWeakMapValues)
       : runtime_(rt)
       , weakMapAction_(weakTraceKind)
 #ifdef DEBUG
       , checkEdges_(true)
 #endif
       , tag_(tag)
+      , traceWeakEdges_(true)
     {}
 
 #ifdef DEBUG
     // Set whether to check edges are valid in debug builds.
     void setCheckEdges(bool check) {
         checkEdges_ = check;
     }
 #endif
@@ -112,16 +114,17 @@ class JS_PUBLIC_API(JSTracer)
     JSRuntime* runtime_;
     WeakMapTraceKind weakMapAction_;
 #ifdef DEBUG
     bool checkEdges_;
 #endif
 
   protected:
     TracerKindTag tag_;
+    bool traceWeakEdges_;
 };
 
 namespace JS {
 
 class AutoTracingName;
 class AutoTracingIndex;
 class AutoTracingCallback;
 
@@ -227,16 +230,21 @@ class JS_PUBLIC_API(CallbackTracer) : pu
     void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); }
     void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); }
     void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); }
     void dispatchToOnEdge(js::BaseShape** basep) { onBaseShapeEdge(basep); }
     void dispatchToOnEdge(js::jit::JitCode** codep) { onJitCodeEdge(codep); }
     void dispatchToOnEdge(js::LazyScript** lazyp) { onLazyScriptEdge(lazyp); }
     void dispatchToOnEdge(js::Scope** scopep) { onScopeEdge(scopep); }
 
+  protected:
+    void setTraceWeakEdges(bool value) {
+        traceWeakEdges_ = value;
+    }
+
   private:
     friend class AutoTracingName;
     const char* contextName_;
 
     friend class AutoTracingIndex;
     size_t contextIndex_;
 
     friend class AutoTracingDetails;
--- a/js/src/builtin/Reflect.cpp
+++ b/js/src/builtin/Reflect.cpp
@@ -21,17 +21,18 @@ using namespace js;
 
 /* ES6 26.1.3 Reflect.defineProperty(target, propertyKey, attributes) */
 static bool
 Reflect_defineProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject obj(cx, NonNullObject(cx, args.get(0)));
+    RootedObject obj(cx, NonNullObjectArg(cx, "`target`", "Reflect.defineProperty",
+                                          args.get(0)));
     if (!obj)
         return false;
 
     // Steps 2-3.
     RootedValue propertyKey(cx, args.get(1));
     RootedId key(cx);
     if (!ToPropertyKey(cx, propertyKey, &key))
         return false;
@@ -51,17 +52,18 @@ Reflect_defineProperty(JSContext* cx, un
 
 /* ES6 26.1.4 Reflect.deleteProperty (target, propertyKey) */
 static bool
 Reflect_deleteProperty(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject target(cx, NonNullObject(cx, args.get(0)));
+    RootedObject target(cx, NonNullObjectArg(cx, "`target`", "Reflect.deleteProperty",
+                                             args.get(0)));
     if (!target)
         return false;
 
     // Steps 2-3.
     RootedValue propertyKey(cx, args.get(1));
     RootedId key(cx);
     if (!ToPropertyKey(cx, propertyKey, &key))
         return false;
@@ -76,17 +78,17 @@ Reflect_deleteProperty(JSContext* cx, un
 
 /* ES6 26.1.6 Reflect.get(target, propertyKey [, receiver]) */
 static bool
 Reflect_get(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject obj(cx, NonNullObject(cx, args.get(0)));
+    RootedObject obj(cx, NonNullObjectArg(cx, "`target`", "Reflect.get", args.get(0)));
     if (!obj)
         return false;
 
     // Steps 2-3.
     RootedValue propertyKey(cx, args.get(1));
     RootedId key(cx);
     if (!ToPropertyKey(cx, propertyKey, &key))
         return false;
@@ -99,32 +101,33 @@ Reflect_get(JSContext* cx, unsigned argc
 }
 
 /* ES6 26.1.7 Reflect.getOwnPropertyDescriptor(target, propertyKey) */
 static bool
 Reflect_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1.
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (!NonNullObject(cx, args.get(0)))
+    if (!NonNullObjectArg(cx, "`target`", "Reflect.getOwnPropertyDescriptor", args.get(0)))
         return false;
 
     // The other steps are identical to ES6 draft rev 32 (2015 Feb 2) 19.1.2.6
     // Object.getOwnPropertyDescriptor.
     return js::obj_getOwnPropertyDescriptor(cx, argc, vp);
 }
 
 /* ES6 26.1.8 Reflect.getPrototypeOf(target) */
 bool
 js::Reflect_getPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject target(cx, NonNullObject(cx, args.get(0)));
+    RootedObject target(cx, NonNullObjectArg(cx, "`target`", "Reflect.getPrototypeOf",
+                                             args.get(0)));
     if (!target)
         return false;
 
     // Step 2.
     RootedObject proto(cx);
     if (!GetPrototype(cx, target, &proto))
         return false;
     args.rval().setObjectOrNull(proto);
@@ -133,17 +136,17 @@ js::Reflect_getPrototypeOf(JSContext* cx
 
 /* ES6 draft 26.1.10 Reflect.isExtensible(target) */
 bool
 js::Reflect_isExtensible(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject target(cx, NonNullObject(cx, args.get(0)));
+    RootedObject target(cx, NonNullObjectArg(cx, "`target`", "Reflect.isExtensible", args.get(0)));
     if (!target)
         return false;
 
     // Step 2.
     bool extensible;
     if (!IsExtensible(cx, target, &extensible))
         return false;
     args.rval().setBoolean(extensible);
@@ -152,31 +155,32 @@ js::Reflect_isExtensible(JSContext* cx, 
 
 /* ES6 26.1.11 Reflect.ownKeys(target) */
 static bool
 Reflect_ownKeys(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    if (!NonNullObject(cx, args.get(0)))
+    if (!NonNullObjectArg(cx, "`target`", "Reflect.ownKeys", args.get(0)))
         return false;
 
     // Steps 2-4.
     return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
 }
 
 /* ES6 26.1.12 Reflect.preventExtensions(target) */
 static bool
 Reflect_preventExtensions(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject target(cx, NonNullObject(cx, args.get(0)));
+    RootedObject target(cx, NonNullObjectArg(cx, "`target`", "Reflect.preventExtensions",
+                                             args.get(0)));
     if (!target)
         return false;
 
     // Step 2.
     ObjectOpResult result;
     if (!PreventExtensions(cx, target, result))
         return false;
     args.rval().setBoolean(bool(result));
@@ -185,17 +189,17 @@ Reflect_preventExtensions(JSContext* cx,
 
 /* ES6 26.1.13 Reflect.set(target, propertyKey, V [, receiver]) */
 static bool
 Reflect_set(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject target(cx, NonNullObject(cx, args.get(0)));
+    RootedObject target(cx, NonNullObjectArg(cx, "`target`", "Reflect.set", args.get(0)));
     if (!target)
         return false;
 
     // Steps 2-3.
     RootedValue propertyKey(cx, args.get(1));
     RootedId key(cx);
     if (!ToPropertyKey(cx, propertyKey, &key))
         return false;
@@ -219,17 +223,17 @@ Reflect_set(JSContext* cx, unsigned argc
  * share code.
  */
 static bool
 Reflect_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
-    RootedObject obj(cx, NonNullObject(cx, args.get(0)));
+    RootedObject obj(cx, NonNullObjectArg(cx, "`target`", "Reflect.setPrototypeOf", args.get(0)));
     if (!obj)
         return false;
 
     // Step 2.
     if (!args.get(1).isObjectOrNull()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
                                   "Reflect.setPrototypeOf", "an object or null",
                                   InformalValueTypeName(args.get(1)));
--- a/js/src/builtin/Reflect.js
+++ b/js/src/builtin/Reflect.js
@@ -29,18 +29,20 @@ function CreateListFromArrayLikeForArgs(
 // ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
 // 26.1.1 Reflect.apply ( target, thisArgument, argumentsList )
 function Reflect_apply(target, thisArgument, argumentsList) {
     // Step 1.
     if (!IsCallable(target))
         ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, target));
 
     // Step 2.
-    if (!IsObject(argumentsList))
-        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(2, argumentsList));
+    if (!IsObject(argumentsList)) {
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT_ARG, "`argumentsList`", "Reflect.apply",
+                       ToSource(argumentsList));
+    }
 
     // Steps 2-4.
     return callFunction(std_Function_apply, target, thisArgument, argumentsList);
 }
 
 // ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
 // 26.1.2 Reflect.construct ( target, argumentsList [ , newTarget ] )
 function Reflect_construct(target, argumentsList/*, newTarget*/) {
@@ -54,18 +56,20 @@ function Reflect_construct(target, argum
         newTarget = arguments[2];
         if (!IsConstructor(newTarget))
             ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(2, newTarget));
     } else {
         newTarget = target;
     }
 
     // Step 4.
-    if (!IsObject(argumentsList))
-        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(1, argumentsList));
+    if (!IsObject(argumentsList)) {
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT_ARG, "`argumentsList`", "Reflect.construct",
+                       ToSource(argumentsList));
+    }
 
     // Fast path when we can avoid calling CreateListFromArrayLikeForArgs().
     var args = (IsPackedArray(argumentsList) && argumentsList.length <= MAX_ARGS_LENGTH)
                ? argumentsList
                : CreateListFromArrayLikeForArgs(argumentsList);
 
     // Step 5.
     switch (args.length) {
@@ -100,13 +104,14 @@ function Reflect_construct(target, argum
     }
 }
 
 // ES2017 draft rev a785b0832b071f505a694e1946182adeab84c972
 // 26.1.8 Reflect.has ( target, propertyKey )
 function Reflect_has(target, propertyKey) {
     // Step 1.
     if (!IsObject(target))
-        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, target));
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT_ARG, "`target`", "Reflect.has",
+                       ToSource(target));
 
     // Steps 2-3 are identical to the runtime semantics of the "in" operator.
     return propertyKey in target;
 }
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -164,21 +164,17 @@ SetWeakMapEntryInternal(JSContext* cx, H
 }
 
 MOZ_ALWAYS_INLINE bool
 WeakMap_set_impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(IsWeakMap(args.thisv()));
 
     if (!args.get(0).isObject()) {
-        UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
-        if (!bytes)
-            return false;
-        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
-                                   bytes.get());
+        ReportNotObjectWithName(cx, "WeakMap key", args.get(0));
         return false;
     }
 
     RootedObject key(cx, &args[0].toObject());
     Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
     Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
 
     if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
--- a/js/src/builtin/WeakSet.js
+++ b/js/src/builtin/WeakSet.js
@@ -28,17 +28,17 @@ function WeakSet_add(value) {
 
     // Step 4.,6.
     let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
     if (!entries)
         ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
 
     // Step 5.
     if (!IsObject(value))
-        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
+        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT_NAME, "WeakSet value", ToSource(value));
 
     // Steps 7-8.
     callFunction(std_WeakMap_set, entries, value, true);
 
     // Step 8.
     return S;
 }
 
--- a/js/src/builtin/WeakSetObject.cpp
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -113,22 +113,17 @@ WeakSetObject::construct(JSContext* cx, 
             RootedValue placeholder(cx, BooleanValue(true));
             RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
             RootedArrayObject array(cx, &iterable.toObject().as<ArrayObject>());
             for (uint32_t index = 0; index < array->getDenseInitializedLength(); ++index) {
                 keyVal.set(array->getDenseElement(index));
                 MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
 
                 if (keyVal.isPrimitive()) {
-                    UniqueChars bytes =
-                        DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr);
-                    if (!bytes)
-                        return false;
-                    JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
-                                               JSMSG_NOT_NONNULL_OBJECT, bytes.get());
+                    ReportNotObjectWithName(cx, "WeakSet value", keyVal);
                     return false;
                 }
 
                 keyObject = &keyVal.toObject();
                 if (!SetWeakMapEntry(cx, map, keyObject, placeholder))
                     return false;
             }
         } else {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -59,39 +59,42 @@ namespace frontend {
 
 using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
 using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
 using BindingIter = ParseContext::Scope::BindingIter;
 using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
 
 // Read a token. Report an error and return null() if that token doesn't match
 // to the condition.  Do not use MUST_MATCH_TOKEN_INTERNAL directly.
-#define MUST_MATCH_TOKEN_INTERNAL(cond, modifier, errorNumber)                              \
+#define MUST_MATCH_TOKEN_INTERNAL(cond, modifier, errorReport)                              \
     JS_BEGIN_MACRO                                                                          \
         TokenKind token;                                                                    \
         if (!tokenStream.getToken(&token, modifier))                                        \
             return null();                                                                  \
         if (!(cond)) {                                                                      \
-            error(errorNumber);                                                             \
+            errorReport;                                                                    \
             return null();                                                                  \
         }                                                                                   \
     JS_END_MACRO
 
 #define MUST_MATCH_TOKEN_MOD(tt, modifier, errorNumber) \
-    MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, errorNumber)
+    MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, error(errorNumber))
 
 #define MUST_MATCH_TOKEN(tt, errorNumber) \
     MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errorNumber)
 
 #define MUST_MATCH_TOKEN_FUNC_MOD(func, modifier, errorNumber) \
-    MUST_MATCH_TOKEN_INTERNAL((func)(token), modifier, errorNumber)
+    MUST_MATCH_TOKEN_INTERNAL((func)(token), modifier, error(errorNumber))
 
 #define MUST_MATCH_TOKEN_FUNC(func, errorNumber) \
     MUST_MATCH_TOKEN_FUNC_MOD(func, TokenStream::None, errorNumber)
 
+#define MUST_MATCH_TOKEN_MOD_WITH_REPORT(tt, modifier, errorReport) \
+    MUST_MATCH_TOKEN_INTERNAL(token == tt, modifier, errorReport)
+
 template <class T, class U>
 static inline void
 PropagateTransitiveParseFlags(const T* inner, U* outer)
 {
     if (inner->bindingsAccessedDynamically())
         outer->setBindingsAccessedDynamically();
     if (inner->hasDebuggerStatement())
         outer->setHasDebuggerStatement();
@@ -998,16 +1001,45 @@ Parser<ParseHandler>::hasValidSimpleStri
         if (!isValidStrictBinding(name->asPropertyName()))
             return false;
     }
     return true;
 }
 
 template <typename ParseHandler>
 void
+Parser<ParseHandler>::reportMissingClosing(unsigned errorNumber, unsigned noteNumber,
+                                           uint32_t openedPos)
+{
+    auto notes = MakeUnique<JSErrorNotes>();
+    if (!notes)
+        return;
+
+    uint32_t line, column;
+    tokenStream.srcCoords.lineNumAndColumnIndex(openedPos, &line, &column);
+
+    const size_t MaxWidth = sizeof("4294967295");
+    char columnNumber[MaxWidth];
+    SprintfLiteral(columnNumber, "%" PRIu32, column);
+    char lineNumber[MaxWidth];
+    SprintfLiteral(lineNumber, "%" PRIu32, line);
+
+    if (!notes->addNoteASCII(pc->sc()->context,
+                             getFilename(), line, column,
+                             GetErrorMessage, nullptr,
+                             noteNumber, lineNumber, columnNumber))
+    {
+        return;
+    }
+
+    errorWithNotes(Move(notes), errorNumber);
+}
+
+template <typename ParseHandler>
+void
 Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind,
                                           TokenPos pos, uint32_t prevPos)
 {
     JSAutoByteString bytes;
     if (!AtomToPrintableString(context, name, &bytes))
         return;
 
     if (prevPos == DeclaredNameInfo::npos) {
@@ -1023,21 +1055,21 @@ Parser<ParseHandler>::reportRedeclaratio
     tokenStream.srcCoords.lineNumAndColumnIndex(prevPos, &line, &column);
 
     const size_t MaxWidth = sizeof("4294967295");
     char columnNumber[MaxWidth];
     SprintfLiteral(columnNumber, "%" PRIu32, column);
     char lineNumber[MaxWidth];
     SprintfLiteral(lineNumber, "%" PRIu32, line);
 
-    if (!notes->addNoteLatin1(pc->sc()->context,
-                              getFilename(), line, column,
-                              GetErrorMessage, nullptr,
-                              JSMSG_REDECLARED_PREV,
-                              lineNumber, columnNumber))
+    if (!notes->addNoteASCII(pc->sc()->context,
+                             getFilename(), line, column,
+                             GetErrorMessage, nullptr,
+                             JSMSG_REDECLARED_PREV,
+                             lineNumber, columnNumber))
     {
         return;
     }
 
     errorWithNotesAt(Move(notes), pos.begin, JSMSG_REDECLARED_VAR,
                      DeclarationKindString(prevKind), bytes.ptr());
 }
 
@@ -3602,16 +3634,17 @@ Parser<ParseHandler>::functionFormalPara
         return false;
     }
 
     // Parse the function body.
     FunctionBodyType bodyType = StatementListBody;
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return false;
+    uint32_t openedPos = 0;
     if (tt != TOK_LC) {
         if (funbox->isStarGenerator() || kind == Method ||
             kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure ||
             IsConstructorKind(kind)) {
             error(JSMSG_CURLY_BEFORE_BODY);
             return false;
         }
 
@@ -3624,16 +3657,18 @@ Parser<ParseHandler>::functionFormalPara
             error(JSMSG_CURLY_BEFORE_BODY);
             return false;
 #endif
         }
 
         tokenStream.ungetToken();
         bodyType = ExpressionBody;
         funbox->setIsExprBody();
+    } else {
+        openedPos = pos().begin;
     }
 
     // Arrow function parameters inherit yieldHandling from the enclosing
     // context, but the arrow body doesn't. E.g. in |(a = yield) => yield|,
     // |yield| in the parameters is either a name or keyword, depending on
     // whether the arrow function is enclosed in a generator function or not.
     // Whereas the |yield| in the function body is always parsed as a name.
     YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind());
@@ -3661,23 +3696,19 @@ Parser<ParseHandler>::functionFormalPara
                                         nameYieldHandling))
             {
                 return false;
             }
         }
     }
 
     if (bodyType == StatementListBody) {
-        bool matched;
-        if (!tokenStream.matchToken(&matched, TOK_RC, TokenStream::Operand))
-            return false;
-        if (!matched) {
-            error(JSMSG_CURLY_AFTER_BODY);
-            return false;
-        }
+        MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
+                                         reportMissingClosing(JSMSG_CURLY_AFTER_BODY,
+                                                              JSMSG_CURLY_OPENED, openedPos));
         funbox->bufEnd = pos().end;
     } else {
 #if !JS_HAS_EXPR_CLOSURES
         MOZ_ASSERT(kind == Arrow);
 #endif
         if (tokenStream.hadError())
             return false;
         funbox->bufEnd = pos().end;
@@ -4441,27 +4472,30 @@ Parser<ParseHandler>::destructuringDecla
     return res;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
+    uint32_t openedPos = pos().begin;
 
     ParseContext::Statement stmt(pc, StatementKind::Block);
     ParseContext::Scope scope(this);
     if (!scope.init(pc))
         return null();
 
     Node list = statementList(yieldHandling);
     if (!list)
         return null();
 
-    MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber);
+    MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
+                                     reportMissingClosing(errorNumber, JSMSG_CURLY_OPENED,
+                                                          openedPos));
 
     return finishLexicalScope(scope, list);
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::expressionAfterForInOrOf(ParseNodeKind forHeadKind,
                                                YieldHandling yieldHandling)
@@ -6700,30 +6734,34 @@ Parser<ParseHandler>::tryStatement(Yield
      *
      * finally nodes are TOK_LC statement lists.
      */
 
     Node innerBlock;
     {
         MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
 
+        uint32_t openedPos = pos().begin;
+
         ParseContext::Statement stmt(pc, StatementKind::Try);
         ParseContext::Scope scope(this);
         if (!scope.init(pc))
             return null();
 
         innerBlock = statementList(yieldHandling);
         if (!innerBlock)
             return null();
 
         innerBlock = finishLexicalScope(scope, innerBlock);
         if (!innerBlock)
             return null();
 
-        MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_TRY);
+        MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
+                                         reportMissingClosing(JSMSG_CURLY_AFTER_TRY,
+                                                              JSMSG_CURLY_OPENED, openedPos));
     }
 
     bool hasUnconditionalCatch = false;
     Node catchList = null();
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
     if (tt == TOK_CATCH) {
@@ -6829,46 +6867,52 @@ Parser<ParseHandler>::tryStatement(Yield
         } while (tt == TOK_CATCH);
     }
 
     Node finallyBlock = null();
 
     if (tt == TOK_FINALLY) {
         MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
 
+        uint32_t openedPos = pos().begin;
+
         ParseContext::Statement stmt(pc, StatementKind::Finally);
         ParseContext::Scope scope(this);
         if (!scope.init(pc))
             return null();
 
         finallyBlock = statementList(yieldHandling);
         if (!finallyBlock)
             return null();
 
         finallyBlock = finishLexicalScope(scope, finallyBlock);
         if (!finallyBlock)
             return null();
 
-        MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_FINALLY);
+        MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
+                                         reportMissingClosing(JSMSG_CURLY_AFTER_FINALLY,
+                                                              JSMSG_CURLY_OPENED, openedPos));
     } else {
         tokenStream.ungetToken();
     }
     if (!catchList && !finallyBlock) {
         error(JSMSG_CATCH_OR_FINALLY);
         return null();
     }
 
     return handler.newTryStatement(begin, innerBlock, catchList, finallyBlock);
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling,
                                           ParseContext::Scope& catchParamScope)
 {
+    uint32_t openedPos = pos().begin;
+
     ParseContext::Statement stmt(pc, StatementKind::Block);
 
     // ES 13.15.7 CatchClauseEvaluation
     //
     // Step 8 means that the body of a catch block always has an additional
     // lexical scope.
     ParseContext::Scope scope(this);
     if (!scope.init(pc))
@@ -6878,17 +6922,19 @@ Parser<ParseHandler>::catchBlockStatemen
     // block, so declare the name in the inner scope.
     if (!scope.addCatchParameters(pc, catchParamScope))
         return null();
 
     Node list = statementList(yieldHandling);
     if (!list)
         return null();
 
-    MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH);
+    MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
+                                     reportMissingClosing(JSMSG_CURLY_AFTER_CATCH,
+                                                          JSMSG_CURLY_OPENED, openedPos));
 
     // The catch parameter names are not bound in the body scope, so remove
     // them before generating bindings.
     scope.removeCatchParameters(pc, catchParamScope);
     return finishLexicalScope(scope, list);
 }
 
 template <typename ParseHandler>
@@ -9214,17 +9260,19 @@ Parser<ParseHandler>::arrayInitializer(Y
                     modifier = TokenStream::None;
                     break;
                 }
                 if (tt == TOK_TRIPLEDOT && possibleError)
                     possibleError->setPendingDestructuringErrorAt(pos(), JSMSG_REST_WITH_COMMA);
             }
         }
 
-        MUST_MATCH_TOKEN_MOD(TOK_RB, modifier, JSMSG_BRACKET_AFTER_LIST);
+        MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, modifier,
+                                         reportMissingClosing(JSMSG_BRACKET_AFTER_LIST,
+                                                              JSMSG_BRACKET_OPENED, begin));
     }
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
 static JSAtom*
 DoubleToAtom(JSContext* cx, double value)
 {
@@ -9435,16 +9483,18 @@ Parser<ParseHandler>::computedPropertyNa
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
 
+    uint32_t openedPos = pos().begin;
+
     Node literal = handler.newObjectLiteral(pos().begin);
     if (!literal)
         return null();
 
     bool seenPrototypeMutation = false;
     bool seenCoverInitializedName = false;
     RootedAtom propAtom(context);
     for (;;) {
@@ -9597,17 +9647,17 @@ Parser<ParseHandler>::objectLiteral(Yiel
                 return null();
         }
 
         if (!tokenStream.getToken(&tt))
             return null();
         if (tt == TOK_RC)
             break;
         if (tt != TOK_COMMA) {
-            error(JSMSG_CURLY_AFTER_LIST);
+            reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED, openedPos);
             return null();
         }
     }
 
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -1443,16 +1443,18 @@ class Parser final : public ParserBase, 
                                        FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls);
 
   private:
     bool checkIncDecOperand(Node operand, uint32_t operandOffset);
     bool checkStrictAssignment(Node lhs);
 
     bool hasValidSimpleStrictParameterNames();
 
+    void reportMissingClosing(unsigned errorNumber, unsigned noteNumber, uint32_t openedPos);
+
     void reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind, TokenPos pos,
                              uint32_t prevPos);
     bool notePositionalFormalParameter(Node fn, HandlePropertyName name, uint32_t beginPos,
                                        bool disallowDuplicateParams, bool* duplicatedParam);
     bool noteDestructuredPositionalFormalParameter(Node fn, Node destruct);
     mozilla::Maybe<DeclarationKind> isVarRedeclaredInEval(HandlePropertyName name,
                                                           DeclarationKind kind);
     bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind, uint32_t beginPos,
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -974,17 +974,17 @@ class GCRuntime
     template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
     void markBufferedGrayRoots(JS::Zone* zone);
     void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
     void markAllWeakReferences(gcstats::Phase phase);
     void markAllGrayReferences(gcstats::Phase phase);
 
     void beginSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
     void findZoneGroups(AutoLockForExclusiveAccess& lock);
-    MOZ_MUST_USE bool findZoneEdgesForWeakMaps();
+    MOZ_MUST_USE bool findInterZoneEdges();
     void getNextZoneGroup();
     void endMarkingZoneGroup();
     void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
     bool shouldReleaseObservedTypes();
     void endSweepingZoneGroup();
     IncrementalProgress sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
     void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
     bool allCCVisibleZonesWereCollected() const;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -474,19 +474,22 @@ js::UnsafeTraceManuallyBarrieredEdge(JST
 {
     DispatchToTracer(trc, ConvertToBase(thingp), name);
 }
 
 template <typename T>
 void
 js::TraceWeakEdge(JSTracer* trc, WeakRef<T>* thingp, const char* name)
 {
-    // Non-marking tracers treat the edge strongly.
-    if (!trc->isMarkingTracer())
-        return DispatchToTracer(trc, ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name);
+    if (!trc->isMarkingTracer()) {
+        // Non-marking tracers can select whether or not they see weak edges.
+        if (trc->traceWeakEdges())
+            DispatchToTracer(trc, ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name);
+        return;
+    }
 
     NoteWeakEdge(GCMarker::fromTracer(trc),
                  ConvertToBase(thingp->unsafeUnbarrieredForTracing()));
 }
 
 template <typename T>
 void
 js::TraceRoot(JSTracer* trc, T* thingp, const char* name)
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -444,24 +444,34 @@ js::gc::GCRuntime::finishVerifier()
     if (verifyPreData) {
         js_delete(verifyPreData.ref());
         verifyPreData = nullptr;
     }
 }
 
 #endif /* JS_GC_ZEAL */
 
-#ifdef JSGC_HASH_TABLE_CHECKS
+#if defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG)
 
-class CheckHeapTracer : public JS::CallbackTracer
+class HeapCheckTracerBase : public JS::CallbackTracer
 {
   public:
-    explicit CheckHeapTracer(JSRuntime* rt);
+    explicit HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind);
     bool init();
-    void check(AutoLockForExclusiveAccess& lock);
+    bool traceHeap(AutoLockForExclusiveAccess& lock);
+    virtual void checkCell(Cell* cell) = 0;
+
+  protected:
+    void dumpCellPath();
+
+    Cell* parentCell() {
+        return parentIndex == -1 ? nullptr : stack[parentIndex].thing.asCell();
+    }
+
+    size_t failures;
 
   private:
     void onChild(const JS::GCCellPtr& thing) override;
 
     struct WorkItem {
         WorkItem(JS::GCCellPtr thing, const char* name, int parentIndex)
           : thing(thing), name(name), parentIndex(parentIndex), processed(false)
         {}
@@ -469,114 +479,216 @@ class CheckHeapTracer : public JS::Callb
         JS::GCCellPtr thing;
         const char* name;
         int parentIndex;
         bool processed;
     };
 
     JSRuntime* rt;
     bool oom;
-    size_t failures;
     HashSet<Cell*, DefaultHasher<Cell*>, SystemAllocPolicy> visited;
     Vector<WorkItem, 0, SystemAllocPolicy> stack;
     int parentIndex;
 };
 
-CheckHeapTracer::CheckHeapTracer(JSRuntime* rt)
-  : CallbackTracer(rt, TraceWeakMapKeysValues),
+HeapCheckTracerBase::HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind)
+  : CallbackTracer(rt, weakTraceKind),
+    failures(0),
     rt(rt),
     oom(false),
-    failures(0),
     parentIndex(-1)
 {
 #ifdef DEBUG
     setCheckEdges(false);
 #endif
 }
 
 bool
-CheckHeapTracer::init()
+HeapCheckTracerBase::init()
 {
     return visited.init();
 }
 
-inline static bool
-IsValidGCThingPointer(Cell* cell)
-{
-    return (uintptr_t(cell) & CellMask) == 0;
-}
-
 void
-CheckHeapTracer::onChild(const JS::GCCellPtr& thing)
+HeapCheckTracerBase::onChild(const JS::GCCellPtr& thing)
 {
     Cell* cell = thing.asCell();
+    checkCell(cell);
+
     if (visited.lookup(cell))
         return;
 
     if (!visited.put(cell)) {
         oom = true;
         return;
     }
 
-    if (!IsValidGCThingPointer(cell) || !IsGCThingValidAfterMovingGC(cell))
-    {
-        failures++;
-        fprintf(stderr, "Bad pointer %p\n", cell);
-        const char* name = contextName();
-        for (int index = parentIndex; index != -1; index = stack[index].parentIndex) {
-            const WorkItem& parent = stack[index];
-            cell = parent.thing.asCell();
-            fprintf(stderr, "  from %s %p %s edge\n",
-                    GCTraceKindToAscii(cell->getTraceKind()), cell, name);
-            name = parent.name;
-        }
-        fprintf(stderr, "  from root %s\n", name);
-        return;
-    }
-
     // Don't trace into GC things owned by another runtime.
     if (cell->runtimeFromAnyThread() != rt)
         return;
 
+    // Don't trace into GC in zones being used by helper threads.
+    Zone* zone = thing.is<JSObject>() ? thing.as<JSObject>().zone() : cell->asTenured().zone();
+    if (zone->group() && zone->group()->usedByHelperThread)
+        return;
+
     WorkItem item(thing, contextName(), parentIndex);
     if (!stack.append(item))
         oom = true;
 }
 
-void
-CheckHeapTracer::check(AutoLockForExclusiveAccess& lock)
+bool
+HeapCheckTracerBase::traceHeap(AutoLockForExclusiveAccess& lock)
 {
     // The analysis thinks that traceRuntime might GC by calling a GC callback.
     JS::AutoSuppressGCAnalysis nogc;
     if (!rt->isBeingDestroyed())
         rt->gc.traceRuntime(this, lock);
 
-    while (!stack.empty()) {
+    while (!stack.empty() && !oom) {
         WorkItem item = stack.back();
         if (item.processed) {
             stack.popBack();
         } else {
             parentIndex = stack.length() - 1;
+            stack.back().processed = true;
             TraceChildren(this, item.thing);
-            stack.back().processed = true;
         }
     }
 
-    if (oom)
+    return !oom;
+}
+
+void
+HeapCheckTracerBase::dumpCellPath()
+{
+    const char* name = contextName();
+    for (int index = parentIndex; index != -1; index = stack[index].parentIndex) {
+        const WorkItem& parent = stack[index];
+        Cell* cell = parent.thing.asCell();
+        fprintf(stderr, "  from %s %p %s edge\n",
+                GCTraceKindToAscii(cell->getTraceKind()), cell, name);
+        name = parent.name;
+    }
+    fprintf(stderr, "  from root %s\n", name);
+}
+
+#endif // defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG)
+
+#ifdef JSGC_HASH_TABLE_CHECKS