merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 28 Sep 2015 14:13:24 +0200
changeset 264719 031db40e2b558c7e4dd0b4c565db4a992c1627c8
parent 264718 3323f5c35ed310823c2febe01bbe748c62055634 (current diff)
parent 264622 fbcf840b4b214162e07b00804a8551f4f7451b5c (diff)
child 264720 2c0e60a8f73631eeb3f38ee502ffe34ec3523a37
child 264812 2bd0998e7c7185d70bbc87c7fe1067889b3480eb
child 264824 43af8bb24e9abd1fff0fd7ba41c459043d305cac
push id65707
push usercbook@mozilla.com
push dateMon, 28 Sep 2015 12:18:34 +0000
treeherdermozilla-inbound@2c0e60a8f736 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/base/nsContentUtils.cpp
dom/ipc/TabParent.cpp
layout/reftests/async-scrolling/bg-fixed-child-clip.html
netwerk/dns/race.c
toolkit/components/telemetry/Histograms.json
toolkit/modules/NewTabUtils.jsm
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1754,29 +1754,21 @@ pref("security.onecrl.maximum_staleness_
 // Override the Gecko-default value of false for Firefox.
 pref("plain_text.wrap_long_lines", true);
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // The request URL of the GeoLocation backend.
-#ifdef RELEASE_BUILD
-pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
-#else
 pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
-#endif
 
 #ifdef XP_MACOSX
-#ifdef RELEASE_BUILD
-pref("geo.provider.use_corelocation", false);
-#else
 pref("geo.provider.use_corelocation", true);
 #endif
-#endif
 
 #ifdef XP_WIN
 pref("geo.provider.ms-windows-location", false);
 #endif
 
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
@@ -1945,8 +1937,14 @@ pref("view_source.tab", true);
 pref("dom.serviceWorkers.enabled", true);
 
 #ifndef RELEASE_BUILD
 pref("dom.serviceWorkers.interception.enabled", true);
 #endif
 
 // Enable Push API.
 pref("dom.push.enabled", true);
+
+// These are the thumbnail width/height set in about:newtab.
+// If you change this, ENSURE IT IS THE SAME SIZE SET
+// by about:newtab. These values are in CSS pixels.
+pref("toolkit.pageThumbs.minWidth", 280);
+pref("toolkit.pageThumbs.minHeight", 190);
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -308,21 +308,28 @@ var gFxAccounts = {
     }
 
     let updateWithProfile = (profile) => {
       if (!this._inCustomizationMode && profileInfoEnabled) {
         if (profile.displayName) {
           this.panelUILabel.setAttribute("label", profile.displayName);
         }
         if (profile.avatar) {
+          this.panelUIFooter.setAttribute("fxaprofileimage", "set");
+          let bgImage = "url(\"" + profile.avatar + "\")";
+          this.panelUIAvatar.style.listStyleImage = bgImage;
+
           let img = new Image();
-          // Make sure the image is available before attempting to display it
-          img.onload = () => {
-            this.panelUIFooter.setAttribute("fxaprofileimage", "set");
-            this.panelUIAvatar.style.listStyleImage = "url('" + profile.avatar + "')";
+          img.onerror = () => {
+            // Clear the image if it has trouble loading. Since this callback is asynchronous
+            // we check to make sure the image is still the same before we clear it.
+            if (this.panelUIAvatar.style.listStyleImage === bgImage) {
+              this.panelUIFooter.removeAttribute("fxaprofileimage");
+              this.panelUIAvatar.style.removeProperty("list-style-image");
+            }
           };
           img.src = profile.avatar;
         }
       }
     }
 
     return fxAccounts.getSignedInUser().then(userData => {
       // userData may be null here when the user is not signed-in, but that's expected
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -329,23 +329,24 @@ var PlacesCommandHook = {
 
     var uri = aBrowser.currentURI;
     var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
     if (itemId == -1) {
       // Bug 1148838 - Make this code work for full page plugins.
       var title;
       var description;
       var charset;
+
+      let docInfo = yield this._getPageDetails(aBrowser);
+
       try {
-        let isErrorPage = /^about:(neterror|certerror|blocked)/
-                          .test(aBrowser.contentDocumentAsCPOW.documentURI);
-        title = isErrorPage ? PlacesUtils.history.getPageTitle(uri)
-                            : aBrowser.contentTitle;
+        title = docInfo.isErrorPage ? PlacesUtils.history.getPageTitle(uri)
+                                    : aBrowser.contentTitle;
         title = title || uri.spec;
-        description = PlacesUIUtils.getDescriptionFromDocument(aBrowser.contentDocumentAsCPOW);
+        description = docInfo.description;
         charset = aBrowser.characterSet;
       }
       catch (e) { }
 
       if (aShowEditUI) {
         // If we bookmark the page here (i.e. page was not "starred" already)
         // but open right into the "edit" state, start batching here, so
         // "Cancel" in that state removes the bookmark.
@@ -400,24 +401,25 @@ var PlacesCommandHook = {
     if (!info) {
       let parentGuid = aParentId !== undefined ?
                          yield PlacesUtils.promiseItemGuid(aParentId) :
                          PlacesUtils.bookmarks.unfiledGuid;
       info = { url, parentGuid };
       // Bug 1148838 - Make this code work for full page plugins.
       let description = null;
       let charset = null;
+
+      let docInfo = yield this._getPageDetails(aBrowser);
+
       try {
-        let isErrorPage = /^about:(neterror|certerror|blocked)/
-                          .test(aBrowser.contentDocumentAsCPOW.documentURI);
-        info.title = isErrorPage ?
+        info.title = docInfo.isErrorPage ?
           (yield PlacesUtils.promisePlaceInfo(aBrowser.currentURI)).title :
           aBrowser.contentTitle;
         info.title = info.title || url.href;
-        description = PlacesUIUtils.getDescriptionFromDocument(aBrowser.contentDocumentAsCPOW);
+        description = docInfo.description;
         charset = aBrowser.characterSet;
       }
       catch (e) {
       	Components.utils.reportError(e);
       }
 
       if (aShowEditUI) {
         // If we bookmark the page here (i.e. page was not "starred" already)
@@ -462,16 +464,28 @@ var PlacesCommandHook = {
     if (isElementVisible(pageProxyFavicon)) {
       StarUI.showEditBookmarkPopup(node, pageProxyFavicon,
                                    "bottomcenter topright");
     } else {
       StarUI.showEditBookmarkPopup(node, aBrowser, "overlap");
     }
   }),
 
+  _getPageDetails(browser) {
+    return new Promise(resolve => {
+      let mm = browser.messageManager;
+      mm.addMessageListener("Bookmarks:GetPageDetails:Result", function listener(msg) {
+        mm.removeMessageListener("Bookmarks:GetPageDetails:Result", listener);
+        resolve(msg.data);
+      });
+
+      mm.sendAsyncMessage("Bookmarks:GetPageDetails", { })
+    });
+  },
+
   /**
    * Adds a bookmark to the page loaded in the current tab. 
    */
   bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
     this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
   },
 
   /**
@@ -563,43 +577,40 @@ var PlacesCommandHook = {
    * Adds a Live Bookmark to a feed associated with the current page. 
    * @param     url
    *            The nsIURI of the page the feed was attached to
    * @title     title
    *            The title of the feed. Optional.
    * @subtitle  subtitle
    *            A short description of the feed. Optional.
    */
-  addLiveBookmark: function PCH_addLiveBookmark(url, feedTitle, feedSubtitle) {
-    var feedURI = makeURI(url);
-
-    var doc = gBrowser.contentDocumentAsCPOW;
-    var title = (arguments.length > 1) ? feedTitle : doc.title;
-
-    var description;
-    if (arguments.length > 2)
-      description = feedSubtitle;
-    else
-      description = PlacesUIUtils.getDescriptionFromDocument(doc);
-
-    var toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId,
+  addLiveBookmark: Task.async(function *(url, feedTitle, feedSubtitle) {
+    let toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId,
                                        PlacesUtils.bookmarks.DEFAULT_INDEX,
                                        Components.interfaces.nsITreeView.DROP_ON);
+
+    let feedURI = makeURI(url);
+    let title = feedTitle || gBrowser.contentTitle;
+    let description = feedSubtitle;
+    if (!description) {
+      description = (yield this._getPageDetails(gBrowser.selectedBrowser)).description;
+    }
+
     PlacesUIUtils.showBookmarkDialog({ action: "add"
                                      , type: "livemark"
                                      , feedURI: feedURI
                                      , siteURI: gBrowser.currentURI
                                      , title: title
                                      , description: description
                                      , defaultInsertionPoint: toolbarIP
                                      , hiddenRows: [ "feedLocation"
                                                    , "siteLocation"
                                                    , "description" ]
                                      }, window);
-  },
+  }),
 
   /**
    * Opens the Places Organizer. 
    * @param   aLeftPaneRoot
    *          The query to select in the organizer window - options
    *          are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
    *          UnfiledBookmarks, Tags and Downloads.
    */
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -748,16 +748,24 @@ addMessageListener("ContextMenu:SearchFi
     let separator = spec.includes("?") ? "&" : "?";
     spec += separator + formData.join("&");
   }
 
   sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
                    { spec, title, description, postData, charset });
 });
 
+addMessageListener("Bookmarks:GetPageDetails", (message) => {
+  let doc = content.document;
+  let isErrorPage = /^about:(neterror|certerror|blocked)/.test(doc.documentURI);
+  sendAsyncMessage("Bookmarks:GetPageDetails:Result",
+                   { isErrorPage: isErrorPage,
+                     description: PlacesUIUtils.getDescriptionFromDocument(doc) });
+});
+
 var LightWeightThemeWebInstallListener = {
   _previewWindow: null,
 
   init: function() {
     addEventListener("InstallBrowserTheme", this, false, true);
     addEventListener("PreviewBrowserTheme", this, false, true);
     addEventListener("ResetBrowserThemePreview", this, false, true);
   },
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -117,16 +117,22 @@ input[type=button] {
   opacity: 0;
 }
 
 #newtab-grid[locked],
 #newtab-grid[page-disabled] {
   pointer-events: none;
 }
 
+/*
+ * If you change the sizes here, make sure you
+ * change the preferences:
+ * toolkit.pageThumbs.minWidth
+ * toolkit.pageThumbs.minHeight
+ */
 /* CELLS */
 .newtab-cell,
 .newtab-intro-cell,
 .newtab-intro-cell-hover {
   display: -moz-box;
   height: 210px;
   margin: 20px 10px 35px;
   width: 290px;
--- a/browser/base/content/test/general/browser_bookmark_titles.js
+++ b/browser/base/content/test/general/browser_bookmark_titles.js
@@ -14,40 +14,29 @@ var tests = [
     // about:neterror
     ['data:application/vnd.mozilla.xul+xml,',
      'data:application/vnd.mozilla.xul+xml,'],
     // about:certerror
     ['https://untrusted.example.com/somepage.html',
      'https://untrusted.example.com/somepage.html']
 ];
 
-function generatorTest() {
+add_task(function* () {
     gBrowser.selectedTab = gBrowser.addTab();
     let browser = gBrowser.selectedBrowser;
     browser.stop(); // stop the about:blank load.
 
-    browser.addEventListener("DOMContentLoaded", event => {
-        if (event.originalTarget != browser.contentDocument ||
-            event.target.location.href == "about:blank") {
-            info("skipping spurious load event");
-            return;
-        }
-        nextStep();
-    }, true);
-    registerCleanupFunction(function () {
-        browser.removeEventListener("DOMContentLoaded", nextStep, true);
-        gBrowser.removeCurrentTab();
-    });
-
     // Test that a bookmark of each URI gets the corresponding default title.
     for (let i = 0; i < tests.length; ++i) {
         let [uri, title] = tests[i];
+
+        let promiseLoaded = promisePageLoaded(browser);
         content.location = uri;
-        yield undefined;
-        checkBookmark(uri, title);
+        yield promiseLoaded;
+        yield checkBookmark(uri, title);
     }
 
     // Network failure test: now that dummy_page.html is in history, bookmarking
     // it should give the last known page title as the default bookmark title.
 
     // Simulate a network outage with offline mode. (Localhost is still
     // accessible in offline mode, so disable the test proxy as well.)
     BrowserOffline.toggleOfflineStatus();
@@ -57,30 +46,56 @@ function generatorTest() {
         BrowserOffline.toggleOfflineStatus();
         Services.prefs.setIntPref('network.proxy.type', proxy);
     });
 
     // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
     Services.cache2.clear();
 
     let [uri, title] = tests[0];
+
+    let promiseLoaded = promisePageLoaded(browser);
     content.location = uri;
-    yield undefined;
+    yield promiseLoaded;
+
     // The offline mode test is only good if the page failed to load.
     is(content.document.documentURI.substring(0, 14), 'about:neterror',
         "Offline mode successfully simulated network outage.");
-    checkBookmark(uri, title);
-}
+    yield checkBookmark(uri, title);
+
+    gBrowser.removeCurrentTab();
+});
 
 // Bookmark the current page and confirm that the new bookmark has the expected
 // title. (Then delete the bookmark.)
-function checkBookmark(uri, expected_title) {
+function* checkBookmark(uri, expected_title) {
     is(gBrowser.selectedBrowser.currentURI.spec, uri,
        "Trying to bookmark the expected uri");
+
+    let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.selectedBrowser.currentURI);
     PlacesCommandHook.bookmarkCurrentPage(false);
+    yield promiseBookmark;
 
     let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri));
     ok(id > 0, "Found the expected bookmark");
     let title = PlacesUtils.bookmarks.getItemTitle(id);
     is(title, expected_title, "Bookmark got a good default title.");
 
     PlacesUtils.bookmarks.removeItem(id);
 }
+
+// BrowserTestUtils.browserLoaded doesn't work for the about pages, so use a
+// custom page load listener.
+function promisePageLoaded(browser)
+{
+  return new Promise(resolve => {
+    browser.addEventListener("DOMContentLoaded", function pageLoaded(event) {
+      browser.removeEventListener("DOMContentLoaded", pageLoaded, true);
+
+      if (event.originalTarget != browser.contentDocument ||
+          event.target.location.href == "about:blank") {
+          info("skipping spurious load event");
+          return;
+      }
+      resolve();
+    }, true);
+  });
+}
--- a/browser/base/content/test/general/browser_star_hsts.js
+++ b/browser/base/content/test/general/browser_star_hsts.js
@@ -19,17 +19,17 @@ add_task(function* test_star_redirect() 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   // This will add the page to the HSTS cache.
   yield promiseTabLoadEvent(tab, secureURL, secureURL);
   // This should transparently be redirected to the secure page.
   yield promiseTabLoadEvent(tab, unsecureURL, secureURL);
 
   yield promiseStarState(BookmarkingUI.STATUS_UNSTARRED);
 
-  let promiseBookmark = promiseOnItemAdded(gBrowser.currentURI);
+  let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI);
   BookmarkingUI.star.click();
   // This resolves on the next tick, so the star should have already been
   // updated at that point.
   yield promiseBookmark;
 
   is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred");
 });
 
@@ -78,37 +78,8 @@ function promiseTabLoadEvent(aTab, aURL,
     }
     aTab.linkedBrowser.removeEventListener("load", load, true);
     info("Tab load event received");
     deferred.resolve();
   }, true, true);
   aTab.linkedBrowser.loadURI(aURL);
   return deferred.promise;
 }
-
-/**
- * Waits for a bookmark to be added for the given uri.
- */
-function promiseOnItemAdded(aExpectedURI) {
-  let defer = Promise.defer();
-  let bookmarksObserver = {
-    onItemAdded: function (aItemId, aFolderId, aIndex, aItemType, aURI) {
-      info("Added a bookmark to " + aURI.spec);
-      PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
-      if (aURI.equals(aExpectedURI))
-        defer.resolve();
-      else
-        defer.reject(new Error("Added an unexpected bookmark"));
-    },
-    onBeginUpdateBatch: function () {},
-    onEndUpdateBatch: function () {},
-    onItemRemoved: function () {},
-    onItemChanged: function () {},
-    onItemVisited: function () {},
-    onItemMoved: function () {},
-    QueryInterface: XPCOMUtils.generateQI([
-      Ci.nsINavBookmarkObserver,
-    ])
-  };
-  info("Waiting for a bookmark to be added");
-  PlacesUtils.bookmarks.addObserver(bookmarksObserver, false);
-  return defer.promise;
-}
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -1067,8 +1067,39 @@ function isSecurityState(expectedState) 
   } else if (isInsecure && !(isSecure || isBroken)) {
     actualState = "insecure";
   } else {
     actualState = "unknown";
   }
 
   is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + ".");
 }
+
+/**
+ * Resolves when a bookmark with the given uri is added.
+ */
+function promiseOnBookmarkItemAdded(aExpectedURI) {
+  return new Promise((resolve, reject) => {
+    let bookmarksObserver = {
+      onItemAdded: function (aItemId, aFolderId, aIndex, aItemType, aURI) {
+        info("Added a bookmark to " + aURI.spec);
+        PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
+        if (aURI.equals(aExpectedURI)) {
+          resolve();
+        }
+        else {
+          reject(new Error("Added an unexpected bookmark"));
+        }
+      },
+      onBeginUpdateBatch: function () {},
+      onEndUpdateBatch: function () {},
+      onItemRemoved: function () {},
+      onItemChanged: function () {},
+      onItemVisited: function () {},
+      onItemMoved: function () {},
+      QueryInterface: XPCOMUtils.generateQI([
+        Ci.nsINavBookmarkObserver,
+      ])
+    };
+    info("Waiting for a bookmark to be added");
+    PlacesUtils.bookmarks.addObserver(bookmarksObserver, false);
+  });
+}
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -2250,17 +2250,17 @@ var CustomizableUIInternal = {
     };
 
     if (typeof aData.id != "string" || !/^[a-z0-9-_]{1,}$/i.test(aData.id)) {
       ERROR("Given an illegal id in normalizeWidget: " + aData.id);
       return null;
     }
 
     delete widget.implementation.currentArea;
-    widget.implementation.__defineGetter__("currentArea", function() widget.currentArea);
+    widget.implementation.__defineGetter__("currentArea", () => widget.currentArea);
 
     const kReqStringProps = ["id"];
     for (let prop of kReqStringProps) {
       if (typeof aData[prop] != "string") {
         ERROR("Missing required property '" + prop + "' in normalizeWidget: "
               + aData.id);
         return null;
       }
@@ -2732,89 +2732,89 @@ var CustomizableUIInternal = {
   },
 };
 Object.freeze(CustomizableUIInternal);
 
 this.CustomizableUI = {
   /**
    * Constant reference to the ID of the menu panel.
    */
-  get AREA_PANEL() "PanelUI-contents",
+  AREA_PANEL: "PanelUI-contents",
   /**
    * Constant reference to the ID of the navigation toolbar.
    */
-  get AREA_NAVBAR() "nav-bar",
+  AREA_NAVBAR: "nav-bar",
   /**
    * Constant reference to the ID of the menubar's toolbar.
    */
-  get AREA_MENUBAR() "toolbar-menubar",
+  AREA_MENUBAR: "toolbar-menubar",
   /**
    * Constant reference to the ID of the tabstrip toolbar.
    */
-  get AREA_TABSTRIP() "TabsToolbar",
+  AREA_TABSTRIP: "TabsToolbar",
   /**
    * Constant reference to the ID of the bookmarks toolbar.
    */
-  get AREA_BOOKMARKS() "PersonalToolbar",
+  AREA_BOOKMARKS: "PersonalToolbar",
   /**
    * Constant reference to the ID of the addon-bar toolbar shim.
    * Do not use, this will be removed as soon as reasonably possible.
    * @deprecated
    */
-  get AREA_ADDONBAR() "addon-bar",
+  AREA_ADDONBAR: "addon-bar",
   /**
    * Constant indicating the area is a menu panel.
    */
-  get TYPE_MENU_PANEL() "menu-panel",
+  TYPE_MENU_PANEL: "menu-panel",
   /**
    * Constant indicating the area is a toolbar.
    */
-  get TYPE_TOOLBAR() "toolbar",
+  TYPE_TOOLBAR: "toolbar",
 
   /**
    * Constant indicating a XUL-type provider.
    */
-  get PROVIDER_XUL() "xul",
+  PROVIDER_XUL: "xul",
   /**
    * Constant indicating an API-type provider.
    */
-  get PROVIDER_API() "api",
+  PROVIDER_API: "api",
   /**
    * Constant indicating dynamic (special) widgets: spring, spacer, and separator.
    */
-  get PROVIDER_SPECIAL() "special",
+  PROVIDER_SPECIAL: "special",
 
   /**
    * Constant indicating the widget is built-in
    */
-  get SOURCE_BUILTIN() "builtin",
+  SOURCE_BUILTIN: "builtin",
   /**
    * Constant indicating the widget is externally provided
    * (e.g. by add-ons or other items not part of the builtin widget set).
    */
-  get SOURCE_EXTERNAL() "external",
+  SOURCE_EXTERNAL: "external",
 
   /**
    * The class used to distinguish items that span the entire menu panel.
    */
-  get WIDE_PANEL_CLASS() "panel-wide-item",
+  WIDE_PANEL_CLASS: "panel-wide-item",
   /**
    * The (constant) number of columns in the menu panel.
    */
-  get PANEL_COLUMN_COUNT() 3,
+  PANEL_COLUMN_COUNT: 3,
 
   /**
    * Constant indicating the reason the event was fired was a window closing
    */
-  get REASON_WINDOW_CLOSED() "window-closed",
+  REASON_WINDOW_CLOSED: "window-closed",
   /**
    * Constant indicating the reason the event was fired was an area being
    * unregistered separately from window closing mechanics.
    */
-  get REASON_AREA_UNREGISTERED() "area-unregistered",
+  REASON_AREA_UNREGISTERED: "area-unregistered",
 
 
   /**
    * An iteratable property of windows managed by CustomizableUI.
    * Note that this can *only* be used as an iterator. ie:
    *     for (let window of CustomizableUI.windows) { ... }
    */
   windows: {
@@ -3672,20 +3672,20 @@ Object.freeze(this.CustomizableUI.window
  */
 function WidgetGroupWrapper(aWidget) {
   this.isGroup = true;
 
   const kBareProps = ["id", "source", "type", "disabled", "label", "tooltiptext",
                       "showInPrivateBrowsing", "viewId"];
   for (let prop of kBareProps) {
     let propertyName = prop;
-    this.__defineGetter__(propertyName, function() aWidget[propertyName]);
+    this.__defineGetter__(propertyName, () => aWidget[propertyName]);
   }
 
-  this.__defineGetter__("provider", function() CustomizableUI.PROVIDER_API);
+  this.__defineGetter__("provider", () => CustomizableUI.PROVIDER_API);
 
   this.__defineSetter__("disabled", function(aValue) {
     aValue = !!aValue;
     aWidget.disabled = aValue;
     for (let [,instance] of aWidget.instances) {
       instance.disabled = aValue;
     }
   });
@@ -3752,20 +3752,20 @@ function WidgetSingleWrapper(aWidget, aN
   }
 
   const kNodeProps = ["label", "tooltiptext"];
   for (let prop of kNodeProps) {
     let propertyName = prop;
     // Look at the node for these, instead of the widget data, to ensure the
     // wrapper always reflects this live instance.
     this.__defineGetter__(propertyName,
-                          function() aNode.getAttribute(propertyName));
+                          () => aNode.getAttribute(propertyName));
   }
 
-  this.__defineGetter__("disabled", function() aNode.disabled);
+  this.__defineGetter__("disabled", () => aNode.disabled);
   this.__defineSetter__("disabled", function(aValue) {
     aNode.disabled = !!aValue;
   });
 
   this.__defineGetter__("anchor", function() {
     let anchorId;
     // First check for an anchor for the area:
     let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
--- a/browser/components/customizableui/PanelWideWidgetTracker.jsm
+++ b/browser/components/customizableui/PanelWideWidgetTracker.jsm
@@ -92,19 +92,19 @@ var PanelWideWidgetTracker = {
       return;
     }
     this.adjusting = true;
     let widgetsAffected = [w for (w of gPanelPlacements) if (gWideWidgets.has(w))];
     // If we're moving the wide widgets forwards (down/to the right in the panel)
     // we want to start with the last widgets. Otherwise we move widgets over other wide
     // widgets, which might mess up their order. Likewise, if moving backwards we should start with
     // the first widget and work our way down/right from there.
-    let compareFn = aMoveForwards ? (function(a, b) a < b) : (function(a, b) a > b)
-    widgetsAffected.sort(function(a, b) compareFn(gPanelPlacements.indexOf(a),
-                                                  gPanelPlacements.indexOf(b)));
+    let compareFn = aMoveForwards ? ((a, b) => a < b) : ((a, b) => a > b);
+    widgetsAffected.sort((a, b) => compareFn(gPanelPlacements.indexOf(a),
+                                             gPanelPlacements.indexOf(b)));
     for (let widget of widgetsAffected) {
       this.adjustPosition(widget, aMoveForwards);
     }
     this.adjusting = false;
   },
   // This function is called whenever an item gets moved in the menu panel. It
   // adjusts the position of widgets within the panel to prevent "gaps" between
   // wide widgets that could be filled up with single column widgets
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -12,17 +12,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/ShortcutUtils.jsm");
 
 /**
  * Maintains the state and dispatches events for the main menu panel.
  */
 
 const PanelUI = {
   /** Panel events that we listen for. **/
-  get kEvents() ["popupshowing", "popupshown", "popuphiding", "popuphidden"],
+  get kEvents() {
+    return ["popupshowing", "popupshown", "popuphiding", "popuphidden"];
+  },
   /**
    * Used for lazily getting and memoizing elements from the document. Lazy
    * getters are set in init, and memoizing happens after the first retrieval.
    */
   get kElements() {
     return {
       contents: "PanelUI-contents",
       mainView: "PanelUI-mainView",
--- a/browser/components/customizableui/content/toolbar.xml
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -283,17 +283,19 @@
       </destructor>
 
       <field name="_inactiveTimeout">null</field>
 
       <field name="_contextMenuListener"><![CDATA[({
         toolbar: this,
         contextMenu: null,
 
-        get active () !!this.contextMenu,
+        get active () {
+          return !!this.contextMenu;
+        },
 
         init: function (event) {
           let node = event.target;
           while (node != this.toolbar) {
             if (node.localName == "menupopup")
               return;
             node = node.parentNode;
           }
--- a/browser/components/customizableui/test/browser_877178_unregisterArea.js
+++ b/browser/components/customizableui/test/browser_877178_unregisterArea.js
@@ -3,29 +3,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 registerCleanupFunction(removeCustomToolbars);
 
 // Sanity checks
 add_task(function sanityChecks() {
-  SimpleTest.doesThrow(function() CustomizableUI.registerArea("@foo"),
+  SimpleTest.doesThrow(() => CustomizableUI.registerArea("@foo"),
                        "Registering areas with an invalid ID should throw.");
 
-  SimpleTest.doesThrow(function() CustomizableUI.registerArea([]),
+  SimpleTest.doesThrow(() => CustomizableUI.registerArea([]),
                        "Registering areas with an invalid ID should throw.");
 
-  SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("@foo"),
+  SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("@foo"),
                        "Unregistering areas with an invalid ID should throw.");
 
-  SimpleTest.doesThrow(function() CustomizableUI.unregisterArea([]),
+  SimpleTest.doesThrow(() => CustomizableUI.unregisterArea([]),
                        "Unregistering areas with an invalid ID should throw.");
 
-  SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("unknown"),
+  SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("unknown"),
                        "Unregistering an area that's not registered should throw.");
 });
 
 // Check areas are loaded with their default placements.
 add_task(function checkLoadedAres() {
   ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
 });
 
--- a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
+++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
@@ -77,17 +77,17 @@ add_task(function() {
   yield waitForCondition(() => navbar.getAttribute("overflowing") == "true");
   ok(!navbar.querySelector("#search-container"), "Search container should be overflowing");
 
   let shownPanelPromise = promiseOverflowShown(window);
   sendWebSearchKeyCommand();
   yield shownPanelPromise;
 
   let chevron = document.getElementById("nav-bar-overflow-button");
-  yield waitForCondition(function() chevron.open);
+  yield waitForCondition(() => chevron.open);
 
   yield waitForSearchBarFocus();
 
   let hiddenPanelPromise = promiseOverflowHidden(window);
   EventUtils.synthesizeKey("VK_ESCAPE", {});
   yield hiddenPanelPromise;
   navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
   window.resizeTo(this.originalWindowWidth, window.outerHeight);
@@ -111,17 +111,17 @@ add_task(function() {
     expectOpenUILinkInCall = true;
     CustomizableUI.removeWidgetFromArea("search-container");
     let placement = CustomizableUI.getPlacementOfWidget("search-container");
     is(placement, null, "Search container should be in palette");
 
     openUILinkInCalled = false;
 
     sendWebSearchKeyCommand();
-    yield waitForCondition(function() openUILinkInCalled);
+    yield waitForCondition(() => openUILinkInCalled);
     ok(openUILinkInCalled, "The search page should have been opened.")
     expectOpenUILinkInCall = false;
   } catch (e) {
     ok(false, e);
   }
   CustomizableUI.reset();
 });
 
--- a/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
+++ b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
@@ -10,16 +10,16 @@ add_task(function() {
   let devButton = document.getElementById("developer-button");
   let downloadsButton = document.getElementById("downloads-button");
   let searchBox = document.getElementById("search-container");
   let palette = document.getElementById("customization-palette");
   ok(devButton && downloadsButton && searchBox && palette, "Stuff should exist");
   simulateItemDrag(devButton, downloadsButton);
   simulateItemDrag(searchBox, palette);
   gCustomizeMode.reset();
-  yield waitForCondition(function() !gCustomizeMode.resetting);
+  yield waitForCondition(() => !gCustomizeMode.resetting);
   ok(CustomizableUI.inDefaultState, "Should be back in default state");
   yield endCustomizing();
 });
 
 add_task(function asyncCleanup() {
   yield resetCustomization();
 });
--- a/browser/components/customizableui/test/browser_938980_navbar_collapsed.js
+++ b/browser/components/customizableui/test/browser_938980_navbar_collapsed.js
@@ -31,17 +31,17 @@ add_task(function() {
   setToolbarVisibility(bookmarksToolbar, true);
   setToolbarVisibility(navbar, false);
   ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now");
   ok(navbar.collapsed, "navbar should be collapsed");
   is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
 
   yield startCustomizing();
   gCustomizeMode.reset();
-  yield waitForCondition(function() !gCustomizeMode.resetting);
+  yield waitForCondition(() => !gCustomizeMode.resetting);
   yield endCustomizing();
 
   is(bookmarksToolbar.collapsed, true, "Customization reset should restore collapsed-state to the bookmarks toolbar");
   ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
   ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
   ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
 });
 
@@ -55,17 +55,17 @@ add_task(function() {
   ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
 
   is(menubar.getBoundingClientRect().height, 0, "menubar should be hidden by default");
   setToolbarVisibility(menubar, true);
   isnot(menubar.getBoundingClientRect().height, 0, "menubar should be visible now");
 
   yield startCustomizing();
   gCustomizeMode.reset();
-  yield waitForCondition(function() !gCustomizeMode.resetting);
+  yield waitForCondition(() => !gCustomizeMode.resetting);
 
   is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset in customization mode");
   is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset in customization mode");
 
   yield endCustomizing();
 
   is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset");
   is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset");
@@ -83,17 +83,17 @@ add_task(function() {
 
   yield startCustomizing();
 
   ok(!bookmarksToolbar.collapsed, "The bookmarksToolbar should be visible before reset");
   ok(!navbar.collapsed, "The navbar should be visible before reset");
   ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
 
   gCustomizeMode.reset();
-  yield waitForCondition(function() !gCustomizeMode.resetting);
+  yield waitForCondition(() => !gCustomizeMode.resetting);
 
   ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
   ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
   ok(!navbar.collapsed, "The navbar should still be visible after reset");
   ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
   yield endCustomizing();
 });
 
--- a/browser/components/customizableui/test/browser_947914_button_addons.js
+++ b/browser/components/customizableui/test/browser_947914_button_addons.js
@@ -13,18 +13,18 @@ add_task(function() {
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let addonsButton = document.getElementById("add-ons-button");
   ok(addonsButton, "Add-ons button exists in Panel Menu");
   addonsButton.click();
 
   newTab = gBrowser.selectedTab;
-  yield waitForCondition(function() gBrowser.currentURI &&
-                                    gBrowser.currentURI.spec == "about:addons");
+  yield waitForCondition(() => gBrowser.currentURI &&
+                               gBrowser.currentURI.spec == "about:addons");
 
   let addonsPage = gBrowser.selectedBrowser.contentWindow.document.
                             getElementById("addons-page");
   ok(addonsPage, "Add-ons page was opened");
 });
 
 add_task(function asyncCleanup() {
   gBrowser.addTab(initialLocation);
--- a/browser/components/customizableui/test/browser_947987_removable_default.js
+++ b/browser/components/customizableui/test/browser_947987_removable_default.js
@@ -8,17 +8,17 @@ var kWidgetId = "test-removable-widget-d
 const kNavBar = CustomizableUI.AREA_NAVBAR;
 var widgetCounter = 0;
 
 registerCleanupFunction(removeCustomToolbars);
 
 // Sanity checks
 add_task(function() {
   let brokenSpec = {id: kWidgetId + (widgetCounter++), removable: false};
-  SimpleTest.doesThrow(function() CustomizableUI.createWidget(brokenSpec),
+  SimpleTest.doesThrow(() => CustomizableUI.createWidget(brokenSpec),
                        "Creating non-removable widget without defaultArea should throw.");
 
   // Widget without removable set should be removable:
   let wrapper = CustomizableUI.createWidget({id: kWidgetId + (widgetCounter++)});
   ok(CustomizableUI.isWidgetRemovable(wrapper.id), "Should be removable by default.");
   CustomizableUI.destroyWidget(wrapper.id);
 });
 
--- a/browser/components/customizableui/test/browser_967000_button_feeds.js
+++ b/browser/components/customizableui/test/browser_967000_button_feeds.js
@@ -28,17 +28,17 @@ add_task(function() {
   PanelUI.hide();
   yield panelHidePromise;
 
   newTab = gBrowser.selectedTab;
   yield promiseTabLoadEvent(newTab, TEST_PAGE);
 
   yield PanelUI.show();
 
-  yield waitForCondition(function() !feedButton.hasAttribute("disabled"));
+  yield waitForCondition(() => !feedButton.hasAttribute("disabled"));
   ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled");
 
   feedButton.click();
   yield promiseTabLoadEvent(newTab, TEST_FEED);
 
   is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened");
   ok(!isPanelUIOpen(), "Panel is closed");
 
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -15,17 +15,17 @@ add_task(function() {
   is(undoResetButton.hidden, true, "The undo button is hidden before reset");
 
   yield gCustomizeMode.reset();
 
   ok(CustomizableUI.inDefaultState, "In default state after reset");
   is(undoResetButton.hidden, false, "The undo button is visible after reset");
 
   undoResetButton.click();
-  yield waitForCondition(function() !gCustomizeMode.resetting);
+  yield waitForCondition(() => !gCustomizeMode.resetting);
   ok(!CustomizableUI.inDefaultState, "Not in default state after reset-undo");
   is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
 
   yield gCustomizeMode.reset();
 });
 
 // Performing an action after a reset will hide the reset button.
--- a/browser/components/customizableui/test/browser_987185_syncButton.js
+++ b/browser/components/customizableui/test/browser_987185_syncButton.js
@@ -43,17 +43,17 @@ add_task(function* asyncCleanup() {
     yield panelHidePromise;
   }
 
   restoreValues();
 });
 
 function mockFunctions() {
   // mock needsSetup
-  gSyncUI._needsSetup = function() Promise.resolve(false);
+  gSyncUI._needsSetup = () => Promise.resolve(false);
 
   // mock service.errorHandler.syncAndReportErrors()
   service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
 }
 
 function mocked_syncAndReportErrors() {
   syncWasCalled = true;
 }
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -369,17 +369,17 @@ function waitForCondition(aConditionFn, 
   let deferred = Promise.defer();
   let tries = 0;
   tryAgain();
   return deferred.promise;
 }
 
 function waitFor(aTimeout=100) {
   let deferred = Promise.defer();
-  setTimeout(function() deferred.resolve(), aTimeout);
+  setTimeout(() => deferred.resolve(), aTimeout);
   return deferred.promise;
 }
 
 /**
  * Starts a load in an existing tab and waits for it to finish (via some event).
  *
  * @param aTab       The tab to load into.
  * @param aUrl       The url to load.
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -49,49 +49,49 @@ DistributionCustomizer.prototype = {
         ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
               getService(Ci.nsIINIParserFactory).
               createINIParser(this._iniFile);
       }
     } catch (e) {
       // Unable to parse INI.
       Cu.reportError("Unable to parse distribution.ini");
     }
-    this.__defineGetter__("_ini", function() 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";
     }
-    this.__defineGetter__("_locale", function() locale);
+    this.__defineGetter__("_locale", () => locale);
     return this._locale;
   },
 
   get _prefSvc() {
     let svc = Cc["@mozilla.org/preferences-service;1"].
               getService(Ci.nsIPrefService);
-    this.__defineGetter__("_prefSvc", function() svc);
+    this.__defineGetter__("_prefSvc", () => svc);
     return this._prefSvc;
   },
 
   get _prefs() {
     let branch = this._prefSvc.getBranch(null);
-    this.__defineGetter__("_prefs", function() branch);
+    this.__defineGetter__("_prefs", () => branch);
     return this._prefs;
   },
 
   get _ioSvc() {
     let svc = Cc["@mozilla.org/network/io-service;1"].
               getService(Ci.nsIIOService);
-    this.__defineGetter__("_ioSvc", function() svc);
+    this.__defineGetter__("_ioSvc", () => svc);
     return this._ioSvc;
   },
 
   _makeURI: function DIST__makeURI(spec) {
     return this._ioSvc.newURI(spec, null, null);
   },
 
   _parseBookmarksSection: Task.async(function* (parentGuid, section) {
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -655,17 +655,19 @@ DownloadsDataCtor.prototype = {
     }
   },
   _dataLinkInitialized: false,
 
   /**
    * Iterator for all the available Download objects. This is empty until the
    * data has been loaded using the JavaScript API for downloads.
    */
-  get downloads() this.oldDownloadStates.keys(),
+  get downloads() {
+    return this.oldDownloadStates.keys();
+  },
 
   /**
    * True if there are finished downloads that can be removed from the list.
    */
   get canRemoveFinished() {
     for (let download of this.downloads) {
       // Stopped, paused, and failed downloads with partial data are removed.
       if (download.stopped && !(download.canceled && download.hasPartialData)) {
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -146,17 +146,19 @@ this.DownloadsViewUI.DownloadElementShel
 
   lastEstimatedSecondsLeft: Infinity,
 
   /**
    * Returns the text for the status line and the associated tooltip. These are
    * returned by a single property because they are computed together. The
    * result may be overridden by derived objects.
    */
-  get statusTextAndTip() this.rawStatusTextAndTip,
+  get statusTextAndTip() {
+    return this.rawStatusTextAndTip;
+  },
 
   /**
    * Derived objects may call this to get the status text.
    */
   get rawStatusTextAndTip() {
     const nsIDM = Ci.nsIDownloadManager;
     let s = DownloadsCommon.strings;
 
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -215,42 +215,50 @@ HistoryDownloadElementShell.prototype = 
    */
   ensureActive() {
     if (!this._active) {
       this._active = true;
       this.element.setAttribute("active", true);
       this._updateUI();
     }
   },
-  get active() !!this._active,
+  get active() {
+    return !!this._active;
+  },
 
   /**
    * Overrides the base getter to return the Download or HistoryDownload object
    * for displaying information and executing commands in the user interface.
    */
-  get download() this._sessionDownload || this._historyDownload,
+  get download() {
+    return this._sessionDownload || this._historyDownload;
+  },
 
   _sessionDownload: null,
-  get sessionDownload() this._sessionDownload,
+  get sessionDownload() {
+    return this._sessionDownload;
+  },
   set sessionDownload(aValue) {
     if (this._sessionDownload != aValue) {
       if (!aValue && !this._historyDownload) {
         throw new Error("Should always have either a Download or a HistoryDownload");
       }
 
       this._sessionDownload = aValue;
 
       this.ensureActive();
       this._updateUI();
     }
     return aValue;
   },
 
   _historyDownload: null,
-  get historyDownload() this._historyDownload,
+  get historyDownload() {
+    return this._historyDownload;
+  },
   set historyDownload(aValue) {
     if (this._historyDownload != aValue) {
       if (!aValue && !this._sessionDownload) {
         throw new Error("Should always have either a Download or a HistoryDownload");
       }
 
       this._historyDownload = aValue;
 
@@ -545,19 +553,23 @@ function DownloadsPlacesView(aRichListBo
   }, true);
   // Resizing the window may change items visibility.
   window.addEventListener("resize", () => {
     this._ensureVisibleElementsAreActive();
   }, true);
 }
 
 DownloadsPlacesView.prototype = {
-  get associatedElement() this._richlistbox,
+  get associatedElement() {
+    return this._richlistbox;
+  },
 
-  get active() this._active,
+  get active() {
+    return this._active;
+  },
   set active(val) {
     this._active = val;
     if (this._active)
       this._ensureVisibleElementsAreActive();
     return this._active;
   },
 
   /**
@@ -925,17 +937,19 @@ DownloadsPlacesView.prototype = {
                                  firstVisibleNode.previousSibling;
       if (nodeAboveVisibleArea && nodeAboveVisibleArea._shell) {
         nodeAboveVisibleArea._shell.ensureActive();
       }
     }, 10);
   },
 
   _place: "",
-  get place() this._place,
+  get place() {
+    return this._place;
+  },
   set place(val) {
     // Don't reload everything if we don't have to.
     if (this._place == val) {
       // XXXmano: places.js relies on this behavior (see Bug 822203).
       this.searchTerm = "";
       return val;
     }
 
@@ -950,17 +964,19 @@ DownloadsPlacesView.prototype = {
 
     let result = history.executeQueries(queries.value, queries.value.length,
                                         options.value);
     result.addObserver(this, false);
     return val;
   },
 
   _result: null,
-  get result() this._result,
+  get result() {
+    return this._result;
+  },
   set result(val) {
     if (this._result == val) {
       return val;
     }
 
     if (this._result) {
       this._result.removeObserver(this);
       this._resultNode.containerOpen = false;
@@ -985,17 +1001,19 @@ DownloadsPlacesView.prototype = {
             element._placesNode];
   },
 
   get selectedNode() {
     let selectedNodes = this.selectedNodes;
     return selectedNodes.length == 1 ? selectedNodes[0] : null;
   },
 
-  get hasSelection() this.selectedNodes.length > 0,
+  get hasSelection() {
+    return this.selectedNodes.length > 0;
+  },
 
   containerStateChanged(aNode, aOldState, aNewState) {
     this.invalidateContainer(aNode)
   },
 
   invalidateContainer(aContainer) {
     if (aContainer != this._resultNode) {
       throw new Error("Unexpected container node");
@@ -1080,19 +1098,23 @@ DownloadsPlacesView.prototype = {
   nodeLastModifiedChanged() {},
   nodeHistoryDetailsChanged() {},
   nodeTagsChanged() {},
   sortingChanged() {},
   nodeMoved() {},
   nodeURIChanged() {},
   batching() {},
 
-  get controller() this._richlistbox.controller,
+  get controller() {
+    return this._richlistbox.controller;
+  },
 
-  get searchTerm() this._searchTerm,
+  get searchTerm() {
+    return this._searchTerm;
+  },
   set searchTerm(aValue) {
     if (this._searchTerm != aValue) {
       for (let element of this._richlistbox.childNodes) {
         element.hidden = !element._shell.matchesSearchTerm(aValue);
       }
       this._ensureVisibleElementsAreActive();
     }
     return this._searchTerm = aValue;
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -93,32 +93,43 @@ const DownloadsPanel = {
 
   /**
    * Internal state of the downloads panel, based on one of the kState
    * constants.  This is not the same state as the XUL panel element.
    */
   _state: 0,
 
   /** The panel is not linked to downloads data yet. */
-  get kStateUninitialized() 0,
+  get kStateUninitialized() {
+    return 0;
+  },
   /** This object is linked to data, but the panel is invisible. */
-  get kStateHidden() 1,
+  get kStateHidden() {
+    return 1;
+  },
   /** The panel will be shown as soon as possible. */
-  get kStateWaitingData() 2,
+  get kStateWaitingData() {
+    return 2;
+  },
   /** The panel is almost shown - we're just waiting to get a handle on the
       anchor. */
-  get kStateWaitingAnchor() 3,
+  get kStateWaitingAnchor() {
+    return 3;
+  },
   /** The panel is open. */
-  get kStateShown() 4,
+  get kStateShown() {
+    return 4;
+  },
 
   /**
    * Location of the panel overlay.
    */
-  get kDownloadsOverlay()
-      "chrome://browser/content/downloads/downloadsOverlay.xul",
+  get kDownloadsOverlay() {
+    return "chrome://browser/content/downloads/downloadsOverlay.xul";
+  },
 
   /**
    * Starts loading the download data in background, without opening the panel.
    * Use showPanel instead to load the data and open the panel at the same time.
    *
    * @param aCallback
    *        Called when initialization is complete.
    */
@@ -1324,17 +1335,19 @@ const DownloadsSummary = {
     }
 
     return this._active = aActive;
   },
 
   /**
    * Returns the active state of the downloads summary.
    */
-  get active() this._active,
+  get active() {
+    return this._active;
+  },
 
   _active: false,
 
   /**
    * Sets whether or not we show the progress bar.
    *
    * @param aShowingProgress
    *        True if we should show the progress bar.
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -35,18 +35,19 @@
  * functional indicator, a placeholder used during customization and in the
  * customization palette, or a neutral view as a temporary anchor for the
  * downloads panel.
  */
 const DownloadsButton = {
   /**
    * Location of the indicator overlay.
    */
-  get kIndicatorOverlay()
-      "chrome://browser/content/downloads/indicatorOverlay.xul",
+  get kIndicatorOverlay() {
+    return "chrome://browser/content/downloads/indicatorOverlay.xul";
+  },
 
   /**
    * Returns a reference to the downloads button position placeholder, or null
    * if not available because it has been removed from the toolbars.
    */
   get _placeholder() {
     return document.getElementById("downloads-button");
   },
--- a/browser/components/extensions/test/browser/browser_ext_simple.js
+++ b/browser/components/extensions/test/browser/browser_ext_simple.js
@@ -5,18 +5,17 @@ add_task(function* test_simple() {
   info("startup complete");
   yield extension.unload();
   info("extension unloaded successfully");
 });
 
 add_task(function* test_background() {
   let extension = ExtensionTestUtils.loadExtension("background");
   info("load complete");
-  yield extension.startup();
-  let x = yield extension.awaitMessage("running");
+  let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]);
   is(x, 1, "got correct value from extension");
   info("startup complete");
   extension.sendMessage(10, 20);
   yield extension.awaitFinish();
   info("test complete");
   yield extension.unload();
   info("extension unloaded successfully");
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_update.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update.js
@@ -25,18 +25,17 @@ add_task(function* () {
 
         browser.tabs.update(tabs[1].id, {active: true}, function() {
           browser.test.sendMessage("check");
         });
       });
     },
   });
 
-  yield extension.startup();
-  yield extension.awaitMessage("check");
+  yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
 
   ok(gBrowser.selectedTab == tab2, "correct tab selected");
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab1);
   yield BrowserTestUtils.removeTab(tab2);
 });
--- a/browser/components/extensions/test/browser/browser_ext_windows_update.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -35,17 +35,16 @@ add_task(function* () {
         browser.windows.update(wins[0].id, {focused: true}, function() {
           browser.test.sendMessage("check");
         });
 
       });
     },
   });
 
-  yield extension.startup();
-  yield extension.awaitMessage("check");
+  yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
 
   yield promiseWaitForFocus(window1);
 
   yield extension.unload();
 
   yield BrowserTestUtils.closeWindow(window2);
-});
\ No newline at end of file
+});
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -419,17 +419,18 @@ FeedResultService.prototype = {
       // "web" should have been handled elsewhere
       LOG("unexpected handler: " + handler);
       // fall through
     case "bookmarks":
       var wm = 
           Cc["@mozilla.org/appshell/window-mediator;1"].
           getService(Ci.nsIWindowMediator);
       var topWindow = wm.getMostRecentWindow("navigator:browser");
-      topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle);
+      topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle)
+                                 .catch(Components.utils.reportError);
       break;
     }
   },
   
   /**
    * See nsIFeedResultService.idl
    */
   addFeedResult: function FRS_addFeedResult(feedResult) {
--- a/browser/components/migration/360seProfileMigrator.js
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -73,18 +73,17 @@ function parseINIStrings(file) {
       obj[section][key] = parser.getString(section, key);
     }
   }
   return obj;
 }
 
 function getHash(aStr) {
   // return the two-digit hexadecimal code for a byte
-  function toHexString(charCode)
-    ("0" + charCode.toString(16)).slice(-2);
+  let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
 
   let hasher = Cc["@mozilla.org/security/hash;1"].
                createInstance(Ci.nsICryptoHash);
   hasher.init(Ci.nsICryptoHash.MD5);
   let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
                      createInstance(Ci.nsIStringInputStream);
   stringStream.data = aStr;
   hasher.updateFromStream(stringStream, -1);
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -240,17 +240,17 @@ FirefoxProfileMigrator.prototype._getRes
   }
 
   return [r for each (r in [places, cookies, passwords, formData,
                             dictionary, bookmarksBackups, session,
                             times, healthReporter]) if (r)];
 };
 
 Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
-  get: function() true
+  get: () => true
 });
 
 
 FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
 FirefoxProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
 FirefoxProfileMigrator.prototype.classID = Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FirefoxProfileMigrator]);
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -39,17 +39,19 @@ let CtypesKernelHelpers = MSMigrationUti
 
 
 function History() {
 }
 
 History.prototype = {
   type: MigrationUtils.resourceTypes.HISTORY,
 
-  get exists() true,
+  get exists() {
+    return true;
+  },
 
   migrate: function H_migrate(aCallback) {
     let places = [];
     let typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
     let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
                             createInstance(Ci.nsISimpleEnumerator);
     while (historyEnumerator.hasMoreElements()) {
       let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
@@ -342,33 +344,35 @@ IE7FormPasswords.prototype = {
 };
 
 function Settings() {
 }
 
 Settings.prototype = {
   type: MigrationUtils.resourceTypes.SETTINGS,
 
-  get exists() true,
+  get exists() {
+    return true;
+  },
 
   migrate: function S_migrate(aCallback) {
     // Converts from yes/no to a boolean.
-    function yesNoToBoolean(v) v == "yes";
+    let yesNoToBoolean = v => v == "yes";
 
     // Converts source format like "en-us,ar-kw;q=0.7,ar-om;q=0.3" into
     // destination format like "en-us, ar-kw, ar-om".
     // Final string is sorted by quality (q=) param.
     function parseAcceptLanguageList(v) {
       return v.match(/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/gi)
               .sort(function (a , b) {
                 let qA = parseFloat(a.split(";q=")[1]) || 1.0;
                 let qB = parseFloat(b.split(";q=")[1]) || 1.0;
                 return qA < qB ? 1 : qA == qB ? 0 : -1;
               })
-              .map(function(a) a.split(";")[0]);
+              .map(a => a.split(";")[0]);
     }
 
     // For reference on some of the available IE Registry settings:
     //  * http://msdn.microsoft.com/en-us/library/cc980058%28v=prot.13%29.aspx
     //  * http://msdn.microsoft.com/en-us/library/cc980059%28v=prot.13%29.aspx
 
     // Note that only settings exposed in our UI should be migrated.
 
@@ -390,41 +394,41 @@ Settings.prototype = {
               yesNoToBoolean);
     this._set(kMainKey,
               "Anchor Underline",
               "browser.underline_anchors",
               yesNoToBoolean);
     this._set(kMainKey,
               "Display Inline Images",
               "permissions.default.image",
-              function (v) yesNoToBoolean(v) ? 1 : 2);
+              v => yesNoToBoolean(v) ? 1 : 2);
     this._set(kMainKey,
               "Move System Caret",
               "accessibility.browsewithcaret",
               yesNoToBoolean);
     this._set("Software\\Microsoft\\Internet Explorer\\Settings",
               "Always Use My Colors",
               "browser.display.document_color_use",
-              function (v) !Boolean(v) ? 0 : 2);
+              v => !Boolean(v) ? 0 : 2);
     this._set("Software\\Microsoft\\Internet Explorer\\Settings",
               "Always Use My Font Face",
               "browser.display.use_document_fonts",
-              function (v) !Boolean(v));
+              v => !Boolean(v));
     this._set(kMainKey,
               "SmoothScroll",
               "general.smoothScroll",
               Boolean);
     this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
               "WarnOnClose",
               "browser.tabs.warnOnClose",
               Boolean);
     this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
               "OpenInForeground",
               "browser.tabs.loadInBackground",
-              function (v) !Boolean(v));
+              v => !Boolean(v));
 
     aCallback(true);
   },
 
   /**
    * Reads a setting from the Registry and stores the converted result into
    * the appropriate Firefox preference.
    * 
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -320,19 +320,23 @@ function getEdgeLocalDataFolder() {
 
 function Bookmarks(migrationType) {
   this._migrationType = migrationType;
 }
 
 Bookmarks.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
 
-  get exists() !!this._favoritesFolder,
+  get exists() {
+    return !!this._favoritesFolder;
+  },
 
-  get importedAppLabel() this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE ? "IE" : "Edge",
+  get importedAppLabel() {
+    return this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE ? "IE" : "Edge";
+  },
 
   __favoritesFolder: null,
   get _favoritesFolder() {
     if (!this.__favoritesFolder) {
       if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
         let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
         if (favoritesFolder.exists() && favoritesFolder.isReadable())
           return this.__favoritesFolder = favoritesFolder;
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -93,17 +93,19 @@ this.MigratorPrototype = {
    *   name - a pretty name to display to the user in the UI
    *
    * Only profiles from which data can be imported should be listed.  Otherwise
    * the behavior of the migration wizard isn't well-defined.
    *
    * For a single-profile source (e.g. safari, ie), this returns null,
    * and not an empty array.  That is the default implementation.
    */
-  get sourceProfiles() null,
+  get sourceProfiles() {
+    return null;
+  },
 
   /**
    * MUST BE OVERRIDDEN.
    *
    * Returns an array of "migration resources" objects for the given profile,
    * or for the "default" profile, if the migrator does not support multiple
    * profiles.
    *
@@ -154,39 +156,45 @@ this.MigratorPrototype = {
    *
    * Startup-only migrators are different in two ways:
    * - they may only be used during startup.
    * - the user-profile is half baked during migration.  The folder exists,
    *   but it's only accessible through MigrationUtils.profileStartup.
    *   The migrator can call MigrationUtils.profileStartup.doStartup
    *   at any point in order to initialize the profile.
    */
-  get startupOnlyMigrator() false,
+  get startupOnlyMigrator() {
+    return false;
+  },
 
   /**
    * OVERRIDE IF AND ONLY IF your migrator supports importing the homepage.
    * @see nsIBrowserProfileMigrator
    */
-  get sourceHomePageURL() "",
+  get sourceHomePageURL() {
+    return "";
+  },
 
   /**
    * Override if the data to migrate is locked/in-use and the user should
    * probably shutdown the source browser.
    */
-  get sourceLocked() false,
+  get sourceLocked() {
+    return false;
+  },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * getResources.
    *
    * @see nsIBrowserProfileMigrator
    */
   getMigrateData: function MP_getMigrateData(aProfile) {
     let types = [r.type for each (r in this._getMaybeCachedResources(aProfile))];
-    return types.reduce(function(a, b) a |= b, 0);
+    return types.reduce((a, b) => a |= b, 0);
   },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * migrate for each resource.
    *
    * @see nsIBrowserProfileMigrator
    */
@@ -261,17 +269,17 @@ this.MigratorPrototype = {
     if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) {
       MigrationUtils.profileStartup.doStartup();
 
       // If we're about to migrate bookmarks, first import the default bookmarks.
       // Note We do not need to do so for the Firefox migrator
       // (=startupOnlyMigrator), as it just copies over the places database
       // from another profile.
       const BOOKMARKS = MigrationUtils.resourceTypes.BOOKMARKS;
-      let migratingBookmarks = resources.some(function(r) r.type == BOOKMARKS);
+      let migratingBookmarks = resources.some(r => r.type == BOOKMARKS);
       if (migratingBookmarks) {
         let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].
                           getService(Ci.nsIObserver);
         browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
 
         // Note doMigrate doesn't care about the success of the import.
         let onImportComplete = function() {
           browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
@@ -504,35 +512,39 @@ this.MigrationUtils = Object.freeze({
       "firefox", "chrome", "chromium"
 #endif
     ];
 
     // If a supported default browser is found check it first
     // so that the wizard defaults to import from that browser.
     let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
     if (defaultBrowserKey)
-      migratorKeysOrdered.sort(function (a, b) b == defaultBrowserKey ? 1 : 0);
+      migratorKeysOrdered.sort((a, b) => b == defaultBrowserKey ? 1 : 0);
 
     for (let migratorKey of migratorKeysOrdered) {
       let migrator = this.getMigrator(migratorKey);
       if (migrator)
         yield migrator;
     }
   },
 
   // Whether or not we're in the process of startup migration
-  get isStartupMigration() gProfileStartup != null,
+  get isStartupMigration() {
+    return gProfileStartup != null;
+  },
 
   /**
    * In the case of startup migration, this is set to the nsIProfileStartup
    * instance passed to ProfileMigrator's migrate.
    *
    * @see showMigrationWizard
    */
-  get profileStartup() gProfileStartup,
+  get profileStartup() {
+    return gProfileStartup;
+  },
 
   /**
    * Show the migration wizard.  On mac, this may just focus the wizard if it's
    * already running, in which case aOpener and aParams are ignored.
    *
    * @param [optional] aOpener
    *        the window that asks to open the wizard.
    * @param [optioanl] aParams
--- a/browser/components/migration/SafariProfileMigrator.js
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -320,45 +320,45 @@ Preferences.prototype = {
   migrate: function MPR_migrate(aCallback) {
     this._mainPreferencesPropertyList.read(aDict => {
       Task.spawn(function* () {
         if (!aDict)
           throw new Error("Could not read preferences file");
 
         this._dict = aDict;
 
-        let invert = function(webkitVal) !webkitVal;
+        let invert = webkitVal => !webkitVal;
         this._set("AutoFillPasswords", "signon.rememberSignons");
         this._set("OpenNewTabsInFront", "browser.tabs.loadInBackground", invert);
         this._set("WebKitJavaScriptCanOpenWindowsAutomatically",
                    "dom.disable_open_during_load", invert);
 
         // layout.spellcheckDefault is a boolean stored as a number.
         this._set("WebContinuousSpellCheckingEnabled",
                   "layout.spellcheckDefault", Number);
 
         // Auto-load images
         // Firefox has an elaborate set of Image preferences. The correlation is:
         // Mode:                            Safari    Firefox
         // Blocked                          FALSE     2
         // Allowed                          TRUE      1
         // Allowed, originating site only   --        3
         this._set("WebKitDisplayImagesKey", "permissions.default.image",
-                  function(webkitVal) webkitVal ? 1 : 2);
+                  webkitVal => webkitVal ? 1 : 2);
 
 #ifdef XP_WIN
         // Cookie-accept policy.
         // For the OS X version, see WebFoundationCookieBehavior.
         // Setting                    Safari          Firefox
         // Always Accept              0               0
         // Accept from Originating    2               1
         // Never Accept               1               2
         this._set("WebKitCookieStorageAcceptPolicy",
           "network.cookie.cookieBehavior",
-          function(webkitVal) webkitVal == 0 ? 0 : webkitVal == 1 ? 2 : 1);
+          webkitVal => webkitVal == 0 ? 0 : webkitVal == 1 ? 2 : 1);
 #endif
 
         this._migrateFontSettings();
         yield this._migrateDownloadsFolder();
 
       }.bind(this)).then(() => aCallback(true), ex => {
         Cu.reportError(ex);
         aCallback(false);
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -827,17 +827,17 @@ nsDefaultCommandLineHandler.prototype = 
         try {
           handURIToExistingBrowser(urilist[0], nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, cmdLine);
           return;
         }
         catch (e) {
         }
       }
 
-      var URLlist = urilist.filter(shouldLoadURI).map(function (u) u.spec);
+      var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
       if (URLlist.length) {
         openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
                    "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
                    URLlist);
       }
 
     }
     else if (!cmdLine.preventDefault) {
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -123,21 +123,23 @@ this.PlacesUIUtils = {
       return param !== undefined ? param : matchedId;
     });
   },
 
   getString: function PUIU_getString(key) {
     return bundle.GetStringFromName(key);
   },
 
-  get _copyableAnnotations() [
-    this.DESCRIPTION_ANNO,
-    this.LOAD_IN_SIDEBAR_ANNO,
-    PlacesUtils.READ_ONLY_ANNO,
-  ],
+  get _copyableAnnotations() {
+    return [
+      this.DESCRIPTION_ANNO,
+      this.LOAD_IN_SIDEBAR_ANNO,
+      PlacesUtils.READ_ONLY_ANNO,
+    ];
+  },
 
   /**
    * Get a transaction for copying a uri item (either a bookmark or a history
    * entry) from one container to another.
    *
    * @param   aData
    *          JSON object of dropped or pasted item properties
    * @param   aContainer
@@ -1432,78 +1434,78 @@ XPCOMUtils.defineLazyGetter(this, "bundl
  *
  * This object will be removed once enough users are converted to the new API.
  */
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() {
   // Ensure PlacesUtils is imported in scope.
   PlacesUtils;
 
   return {
-    aggregateTransactions: function(aName, aTransactions)
+    aggregateTransactions: (aName, aTransactions) =>
       new PlacesAggregatedTransaction(aName, aTransactions),
 
-    createFolder: function(aName, aContainer, aIndex, aAnnotations,
-                           aChildItemsTransactions)
+    createFolder: (aName, aContainer, aIndex, aAnnotations,
+                   aChildItemsTransactions) =>
       new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations,
                                         aChildItemsTransactions),
 
-    createItem: function(aURI, aContainer, aIndex, aTitle, aKeyword,
-                         aAnnotations, aChildTransactions)
+    createItem: (aURI, aContainer, aIndex, aTitle, aKeyword,
+                 aAnnotations, aChildTransactions) =>
       new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle,
                                           aKeyword, aAnnotations,
                                           aChildTransactions),
 
-    createSeparator: function(aContainer, aIndex)
+    createSeparator: (aContainer, aIndex) =>
       new PlacesCreateSeparatorTransaction(aContainer, aIndex),
 
-    createLivemark: function(aFeedURI, aSiteURI, aName, aContainer, aIndex,
-                             aAnnotations)
+    createLivemark: (aFeedURI, aSiteURI, aName, aContainer, aIndex,
+                     aAnnotations) =>
       new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer,
                                           aIndex, aAnnotations),
 
-    moveItem: function(aItemId, aNewContainer, aNewIndex)
+    moveItem: (aItemId, aNewContainer, aNewIndex) =>
       new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex),
 
-    removeItem: function(aItemId)
+    removeItem: (aItemId) =>
       new PlacesRemoveItemTransaction(aItemId),
 
-    editItemTitle: function(aItemId, aNewTitle)
+    editItemTitle: (aItemId, aNewTitle) =>
       new PlacesEditItemTitleTransaction(aItemId, aNewTitle),
 
-    editBookmarkURI: function(aItemId, aNewURI)
+    editBookmarkURI: (aItemId, aNewURI) =>
       new PlacesEditBookmarkURITransaction(aItemId, aNewURI),
 
-    setItemAnnotation: function(aItemId, aAnnotationObject)
+    setItemAnnotation: (aItemId, aAnnotationObject) =>
       new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject),
 
-    setPageAnnotation: function(aURI, aAnnotationObject)
+    setPageAnnotation: (aURI, aAnnotationObject) =>
       new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject),
 
-    editBookmarkKeyword: function(aItemId, aNewKeyword)
+    editBookmarkKeyword: (aItemId, aNewKeyword) =>
       new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword),
 
-    editLivemarkSiteURI: function(aLivemarkId, aSiteURI)
+    editLivemarkSiteURI: (aLivemarkId, aSiteURI) =>
       new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI),
 
-    editLivemarkFeedURI: function(aLivemarkId, aFeedURI)
+    editLivemarkFeedURI: (aLivemarkId, aFeedURI) =>
       new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI),
 
-    editItemDateAdded: function(aItemId, aNewDateAdded)
+    editItemDateAdded: (aItemId, aNewDateAdded) =>
       new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded),
 
-    editItemLastModified: function(aItemId, aNewLastModified)
+    editItemLastModified: (aItemId, aNewLastModified) =>
       new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified),
 
-    sortFolderByName: function(aFolderId)
+    sortFolderByName: (aFolderId) =>
       new PlacesSortFolderByNameTransaction(aFolderId),
 
-    tagURI: function(aURI, aTags)
+    tagURI: (aURI, aTags) =>
       new PlacesTagURITransaction(aURI, aTags),
 
-    untagURI: function(aURI, aTags)
+    untagURI: (aURI, aTags) =>
       new PlacesUntagURITransaction(aURI, aTags),
 
     /**
      * Transaction for setting/unsetting Load-in-sidebar annotation.
      *
      * @param aBookmarkId
      *        id of the bookmark where to set Load-in-sidebar annotation.
      * @param aLoadInSidebar
@@ -1537,54 +1539,58 @@ XPCOMUtils.defineLazyGetter(PlacesUIUtil
                       value: aDescription,
                       expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
       return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
     },
 
     ////////////////////////////////////////////////////////////////////////////
     //// nsITransactionManager forwarders.
 
-    beginBatch: function()
+    beginBatch: () =>
       PlacesUtils.transactionManager.beginBatch(null),
 
-    endBatch: function()
+    endBatch: () =>
       PlacesUtils.transactionManager.endBatch(false),
 
-    doTransaction: function(txn)
+    doTransaction: (txn) =>
       PlacesUtils.transactionManager.doTransaction(txn),
 
-    undoTransaction: function()
+    undoTransaction: () =>
       PlacesUtils.transactionManager.undoTransaction(),
 
-    redoTransaction: function()
+    redoTransaction: () =>
       PlacesUtils.transactionManager.redoTransaction(),
 
-    get numberOfUndoItems()
-      PlacesUtils.transactionManager.numberOfUndoItems,
-    get numberOfRedoItems()
-      PlacesUtils.transactionManager.numberOfRedoItems,
-    get maxTransactionCount()
-      PlacesUtils.transactionManager.maxTransactionCount,
-    set maxTransactionCount(val)
-      PlacesUtils.transactionManager.maxTransactionCount = val,
+    get numberOfUndoItems() {
+      return PlacesUtils.transactionManager.numberOfUndoItems;
+    },
+    get numberOfRedoItems() {
+      return PlacesUtils.transactionManager.numberOfRedoItems;
+    },
+    get maxTransactionCount() {
+      return PlacesUtils.transactionManager.maxTransactionCount;
+    },
+    set maxTransactionCount(val) {
+      PlacesUtils.transactionManager.maxTransactionCount = val;
+    },
 
-    clear: function()
+    clear: () =>
       PlacesUtils.transactionManager.clear(),
 
-    peekUndoStack: function()
+    peekUndoStack: () =>
       PlacesUtils.transactionManager.peekUndoStack(),
 
-    peekRedoStack: function()
+    peekRedoStack: () =>
       PlacesUtils.transactionManager.peekRedoStack(),
 
-    getUndoStack: function()
+    getUndoStack: () =>
       PlacesUtils.transactionManager.getUndoStack(),
 
-    getRedoStack: function()
+    getRedoStack: () =>
       PlacesUtils.transactionManager.getRedoStack(),
 
-    AddListener: function(aListener)
+    AddListener: (aListener) =>
       PlacesUtils.transactionManager.AddListener(aListener),
 
-    RemoveListener: function(aListener)
+    RemoveListener: (aListener) =>
       PlacesUtils.transactionManager.RemoveListener(aListener)
   }
 });
--- a/browser/components/places/content/bookmarksPanel.js
+++ b/browser/components/places/content/bookmarksPanel.js
@@ -15,11 +15,10 @@ function searchBookmarks(aSearchString) 
   else
     tree.applyFilter(aSearchString,
                      [PlacesUtils.bookmarksMenuFolderId,
                       PlacesUtils.unfiledBookmarksFolderId,
                       PlacesUtils.toolbarFolderId]);
 }
 
 window.addEventListener("SidebarFocused",
-                        function()
-                          document.getElementById("search-box").focus(),
+                        () => document.getElementById("search-box").focus(),
                         false);
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -54,29 +54,33 @@ function InsertionPoint(aItemId, aIndex,
   this.dropNearItemId = aDropNearItemId;
 }
 
 InsertionPoint.prototype = {
   set index(val) {
     return this._index = val;
   },
 
-  promiseGuid: function () PlacesUtils.promiseItemGuid(this.itemId),
+  promiseGuid: function () {
+    return PlacesUtils.promiseItemGuid(this.itemId);
+  },
 
   get index() {
     if (this.dropNearItemId > 0) {
       // If dropNearItemId is set up we must calculate the real index of
       // the item near which we will drop.
       var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId);
       return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
     }
     return this._index;
   },
 
-  get isTag() typeof(this.tagName) == "string"
+  get isTag() {
+    return typeof(this.tagName) == "string";
+  }
 };
 
 /**
  * Places Controller
  */
 
 function PlacesController(aView) {
   this._view = aView;
@@ -1194,17 +1198,19 @@ PlacesController.prototype = {
     if (hasData) {
       this.clipboard.setData(xferable,
                              this.cutNodes.length > 0 ? this : null,
                              Ci.nsIClipboard.kGlobalClipboard);
     }
   },
 
   _cutNodes: [],
-  get cutNodes() this._cutNodes,
+  get cutNodes() {
+    return this._cutNodes;
+  },
   set cutNodes(aNodes) {
     let self = this;
     function updateCutNodes(aValue) {
       self._cutNodes.forEach(function (aNode) {
         self._view.toggleCutNode(aNode, aValue);
       });
     }
 
@@ -1263,17 +1269,17 @@ PlacesController.prototype = {
     let xferable = Cc["@mozilla.org/widget/transferable;1"].
                    createInstance(Ci.nsITransferable);
     xferable.init(null);
     // This order matters here!  It controls the preferred flavors for this
     // paste operation.
     [ PlacesUtils.TYPE_X_MOZ_PLACE,
       PlacesUtils.TYPE_X_MOZ_URL,
       PlacesUtils.TYPE_UNICODE,
-    ].forEach(function (type) xferable.addDataFlavor(type));
+    ].forEach(type => xferable.addDataFlavor(type));
 
     this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
 
     // Now get the clipboard contents, in the best available flavor.
     let data = {}, type = {}, items = [];
     try {
       xferable.getAnyTransferData(type, data, {});
       data = data.value.QueryInterface(Ci.nsISupportsString).data;
@@ -1383,28 +1389,30 @@ PlacesController.prototype = {
 
   /**
    * Returns whether or not there's cached mozILivemarkInfo object for a node.
    * @param aNode
    *        a places result node.
    * @return true if there's a cached mozILivemarkInfo object for
    *         aNode, false otherwise.
    */
-  hasCachedLivemarkInfo: function PC_hasCachedLivemarkInfo(aNode)
-    this._cachedLivemarkInfoObjects.has(aNode),
+  hasCachedLivemarkInfo: function PC_hasCachedLivemarkInfo(aNode) {
+    return this._cachedLivemarkInfoObjects.has(aNode);
+  },
 
   /**
    * Returns the cached livemark info for a node, if set by cacheLivemarkInfo,
    * null otherwise.
    * @param aNode
    *        a places result node.
    * @return the mozILivemarkInfo object for aNode, if set, null otherwise.
    */
-  getCachedLivemarkInfo: function PC_getCachedLivemarkInfo(aNode)
-    this._cachedLivemarkInfoObjects.get(aNode, null)
+  getCachedLivemarkInfo: function PC_getCachedLivemarkInfo(aNode) {
+    return this._cachedLivemarkInfoObjects.get(aNode, null);
+  }
 };
 
 /**
  * Handles drag and drop operations for views. Note that this is view agnostic!
  * You should not use PlacesController._view within these methods, since
  * the view that the item(s) have been dropped on was not necessarily active.
  * Drop functions are passed the view that is being dropped on.
  */
--- a/browser/components/places/content/downloadsViewOverlay.xul
+++ b/browser/components/places/content/downloadsViewOverlay.xul
@@ -14,17 +14,17 @@
 
   <script type="application/javascript"><![CDATA[
     const DOWNLOADS_QUERY = "place:transition=" +
       Components.interfaces.nsINavHistoryService.TRANSITION_DOWNLOAD +
       "&sort=" +
       Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
 
     ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY,
-      function() new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
+      () => new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
       { showDetailsPane: false,
         toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" });
   ]]></script>
 
   <window id="places">
     <commandset id="downloadCommands"/>
     <menupopup id="downloadsContextMenu"/>
   </window>
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -707,17 +707,17 @@ var gEditItemOverlay = {
     if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
       // reset the selection back to where it was and expand the tree
       // (this menu-item is hidden when the tree is already visible
       let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId);
       let item = this._getFolderMenuItem(containerId);
       this._folderMenuList.selectedItem = item;
       // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
       // menulist right away
-      setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
+      setTimeout(() => this.toggleFolderTreeVisibility(), 100);
       return;
     }
 
     // Move the item
     let containerId = this._getFolderIdFromMenuList();
     if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId &&
         this._paneInfo.itemId != containerId) {
       if (PlacesUIUtils.useAsyncTransactions) {
@@ -901,17 +901,17 @@ var gEditItemOverlay = {
    * Splits "tagsField" element value, returning an array of valid tag strings.
    *
    * @return Array of tag strings found in the field value.
    */
   _getTagsArrayFromTagsInputField() {
     let tags = this._element("tagsField").value;
     return tags.trim()
                .split(/\s*,\s*/) // Split on commas and remove spaces.
-               .filter(function (tag) tag.length > 0); // Kill empty tags.
+               .filter(tag => tag.length > 0); // Kill empty tags.
   },
 
   newFolder: Task.async(function* () {
     let ip = this._folderTree.insertionPoint;
 
     // default to the bookmarks menu folder
     if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
       ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
--- a/browser/components/places/content/history-panel.js
+++ b/browser/components/places/content/history-panel.js
@@ -89,11 +89,10 @@ function searchHistory(aInput)
   // otherwise, we will end up calling load() twice
   gHistoryTree.load([query], options);
 
   if (gHistoryGrouping == "lastvisited")
     this.TelemetryStopwatch.finish("HISTORY_LASTVISITED_TREE_QUERY_TIME_MS");
 }
 
 window.addEventListener("SidebarFocused",
-                        function()
-                          gSearchBox.focus(),
+                        () => gSearchBox.focus(),
                         false);
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -589,17 +589,17 @@ var PlacesOrganizer = {
       infoBox.removeAttribute("minimal");
       infoBoxExpanderWrapper.hidden = true;
     }
     else {
       if (infoBox.getAttribute("wasminimal") == "true")
         infoBox.setAttribute("minimal", "true");
       infoBox.removeAttribute("wasminimal");
       infoBoxExpanderWrapper.hidden =
-        this._additionalInfoFields.every(function (id)
+        this._additionalInfoFields.every(id =>
           document.getElementById(id).collapsed);
     }
     additionalInfoBroadcaster.hidden = infoBox.getAttribute("minimal") == "true";
   },
 
   // NOT YET USED
   updateThumbnailProportions: function PO_updateThumbnailProportions() {
     var previewBox = document.getElementById("previewBox");
@@ -1303,31 +1303,35 @@ var ContentArea = {
     if (!aQueryString ||
         typeof aView != "object" && typeof aView != "function")
       throw new Error("Invalid arguments");
 
     this._specialViews.set(aQueryString, { view: aView,
                                            options: aOptions || new Object() });
   },
 
-  get currentView() PlacesUIUtils.getViewForNode(this._deck.selectedPanel),
+  get currentView() {
+    return PlacesUIUtils.getViewForNode(this._deck.selectedPanel);
+  },
   set currentView(aNewView) {
     let oldView = this.currentView;
     if (oldView != aNewView) {
       this._deck.selectedPanel = aNewView.associatedElement;
 
       // If the content area inactivated view was focused, move focus
       // to the new view.
       if (document.activeElement == oldView.associatedElement)
         aNewView.associatedElement.focus();
     }
     return aNewView;
   },
 
-  get currentPlace() this.currentView.place,
+  get currentPlace() {
+    return this.currentView.place;
+  },
   set currentPlace(aQueryString) {
     let oldView = this.currentView;
     let newView = this.getContentViewForQueryString(aQueryString);
     newView.place = aQueryString;
     if (oldView != newView) {
       oldView.active = false;
       this.currentView = newView;
       this._setupView();
@@ -1382,22 +1386,26 @@ var ContentArea = {
   }
 };
 
 var ContentTree = {
   init: function CT_init() {
     this._view = document.getElementById("placeContent");
   },
 
-  get view() this._view,
+  get view() {
+    return this._view;
+  },
 
-  get viewOptions() Object.seal({
-    showDetailsPane: true,
-    toolbarSet: "back-button, forward-button, organizeButton, viewMenu, maintenanceButton, libraryToolbarSpacer, searchFilter"
-  }),
+  get viewOptions() {
+    return Object.seal({
+      showDetailsPane: true,
+      toolbarSet: "back-button, forward-button, organizeButton, viewMenu, maintenanceButton, libraryToolbarSpacer, searchFilter"
+    });
+  },
 
   openSelectedNode: function CT_openSelectedNode(aEvent) {
     let view = this.view;
     PlacesUIUtils.openNodeWithEvent(view.selectedNode, aEvent, view);
   },
 
   onClick: function CT_onClick(aEvent) {
     let node = this.view.selectedNode;
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -16,17 +16,19 @@ function PlacesTreeView(aFlatList, aOnOp
   this._rootNode = null;
   this._rows = [];
   this._flatList = aFlatList;
   this._openContainerCallback = aOnOpenFlatContainer;
   this._controller = aController;
 }
 
 PlacesTreeView.prototype = {
-  get wrappedJSObject() this,
+  get wrappedJSObject() {
+    return this;
+  },
 
   __xulStore: null,
   get _xulStore() {
     if (!this.__xulStore) {
       this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
     }
     return this.__xulStore;
   },
@@ -1062,17 +1064,19 @@ PlacesTreeView.prototype = {
         this._tree.beginUpdateBatch();
       }
       else {
         this._tree.endUpdateBatch();
       }
     }
   },
 
-  get result() this._result,
+  get result() {
+    return this._result;
+  },
   set result(val) {
     if (this._result) {
       this._result.removeObserver(this);
       this._rootNode.containerOpen = false;
     }
 
     if (val) {
       this._result = val;
@@ -1107,19 +1111,25 @@ PlacesTreeView.prototype = {
       return this._getRowForNode(aNode, true);
     }
     catch(ex) { }
 
     return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
   },
 
   // nsITreeView
-  get rowCount() this._rows.length,
-  get selection() this._selection,
-  set selection(val) this._selection = val,
+  get rowCount() {
+    return this._rows.length;
+  },
+  get selection() {
+    return this._selection;
+  },
+  set selection(val) {
+    this._selection = val;
+  },
 
   getRowProperties: function() { return ""; },
 
   getCellProperties:
   function PTV_getCellProperties(aRow, aColumn) {
     // for anonid-trees, we need to add the column-type manually
     var props = "";
     let columnType = aColumn.element.getAttribute("anonid");
@@ -1389,17 +1399,19 @@ PlacesTreeView.prototype = {
         return true;
       if (nextLevel < thisLevel)
         break;
     }
 
     return false;
   },
 
-  getLevel: function(aRow) this._getNodeForRow(aRow).indentLevel,
+  getLevel: function(aRow) {
+    return this._getNodeForRow(aRow).indentLevel;
+  },
 
   getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
     // Only the title column has an image.
     if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
       return "";
 
     let node = this._getNodeForRow(aRow);
     if (PlacesUtils.nodeIsURI(node) && node.icon)
--- a/browser/components/places/tests/browser/browser_library_search.js
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -144,17 +144,17 @@ function search(aFolderId, aSearchStr, a
   }
 }
 
 /**
  * test() contains window-launching boilerplate that calls this to really kick
  * things off.  Add functions to the testCases array, and this will call them.
  */
 function onLibraryAvailable() {
-  testCases.forEach(function (aTest) aTest());
+  testCases.forEach(aTest => aTest());
 
   gLibrary.close();
   gLibrary = null;
 
   // Cleanup.
   PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]);
   PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
   PlacesTestUtils.clearHistory().then(finish);
--- a/browser/components/places/tests/browser/browser_toolbar_migration.js
+++ b/browser/components/places/tests/browser/browser_toolbar_migration.js
@@ -5,17 +5,19 @@
 /**
  * Tests PersonalToolbar migration path.
  */
 var bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
 var gOriginalMigrationVersion;
 const BROWSER_URL = getBrowserURL();
 
 var localStore = {
-  get xulStore() Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore),
+  get xulStore() {
+    return Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+  },
 
   getValue: function getValue(aProperty)
   {
     return this.xulStore.getValue(BROWSER_URL, "PersonalToolbar", aProperty);
   },
 
   setValue: function setValue(aProperty, aValue)
   {
--- a/browser/components/places/tests/unit/test_PUIU_makeTransaction.js
+++ b/browser/components/places/tests/unit/test_PUIU_makeTransaction.js
@@ -10,47 +10,57 @@ function waitForBookmarkNotification(aNo
           (!aProperty || aProperty == aData.property)) {
         PlacesUtils.bookmarks.removeObserver(this);
         aCallback(aData);
       }
     },
 
     // nsINavBookmarkObserver
     QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
-    onBeginUpdateBatch: function onBeginUpdateBatch()
-      this.validate(arguments.callee.name, arguments),
-    onEndUpdateBatch: function onEndUpdateBatch()
-      this.validate(arguments.callee.name, arguments),
+    onBeginUpdateBatch: function onBeginUpdateBatch() {
+      return this.validate(arguments.callee.name, arguments);
+    },
+    onEndUpdateBatch: function onEndUpdateBatch() {
+      return this.validate(arguments.callee.name, arguments);
+    },
     onItemAdded: function onItemAdded(aItemId, aParentId, aIndex, aItemType,
                                       aURI, aTitle)
     {
       return this.validate(arguments.callee.name, { id: aItemId,
                                                     index: aIndex,
                                                     type: aItemType,
                                                     url: aURI ? aURI.spec : null,
                                                     title: aTitle });
     },
-    onItemRemoved: function onItemRemoved()
-      this.validate(arguments.callee.name, arguments),
+    onItemRemoved: function onItemRemoved() {
+      return this.validate(arguments.callee.name, arguments);
+    },
     onItemChanged: function onItemChanged(aItemId, aProperty, aIsAnno,
                                           aNewValue, aLastModified, aItemType)
     {
       return this.validate(arguments.callee.name,
                            { id: aItemId,
-                             get index() PlacesUtils.bookmarks.getItemIndex(this.id),
+                             get index() {
+                               return PlacesUtils.bookmarks.getItemIndex(this.id);
+                             },
                              type: aItemType,
                              property: aProperty,
-                             get url() aItemType == PlacesUtils.bookmarks.TYPE_BOOKMARK ?
-                                                PlacesUtils.bookmarks.getBookmarkURI(this.id).spec :
-                                                null,
-                             get title() PlacesUtils.bookmarks.getItemTitle(this.id),
+                             get url() {
+                               return aItemType == PlacesUtils.bookmarks.TYPE_BOOKMARK ?
+                                      PlacesUtils.bookmarks.getBookmarkURI(this.id).spec :
+                                      null;
+                             },
+                             get title() {
+                               return PlacesUtils.bookmarks.getItemTitle(this.id);
+                             },
                            });
     },
-    onItemVisited: function onItemVisited()
-      this.validate(arguments.callee.name, arguments),
+    onItemVisited: function onItemVisited() {
+      return this.validate(arguments.callee.name, arguments);
+    },
     onItemMoved: function onItemMoved(aItemId, aOldParentId, aOldIndex,
                                       aNewParentId, aNewIndex, aItemType)
     {
       this.validate(arguments.callee.name, { id: aItemId,
                                              index: aNewIndex,
                                              type: aItemType });
     }
   }, false);
--- a/browser/components/pocket/Pocket.jsm
+++ b/browser/components/pocket/Pocket.jsm
@@ -11,17 +11,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 
 var Pocket = {
-  get site() Services.prefs.getCharPref("browser.pocket.site"),
+  get site() { return Services.prefs.getCharPref("browser.pocket.site"); },
   get listURL() { return "https://" + Pocket.site + "/?src=ff_ext"; },
 
   /**
    * Functions related to the Pocket panel UI.
    */
   onPanelViewShowing(event) {
     let document = event.target.ownerDocument;
     let window = document.defaultView;
--- a/browser/components/preferences/aboutPermissions.js
+++ b/browser/components/preferences/aboutPermissions.js
@@ -326,18 +326,22 @@ var PermissionDefaults = {
       return this.DENY;
     }
     return this.UNKNOWN;
   },
   set push(aValue) {
     let value = (aValue != this.DENY);
     Services.prefs.setBoolPref("dom.push.enabled", value);
   },
-  get camera() this.UNKNOWN,
-  get microphone() this.UNKNOWN
+  get camera() {
+    return this.UNKNOWN;
+  },
+  get microphone() {
+    return this.UNKNOWN;
+  }
 };
 
 /**
  * AboutPermissions manages the about:permissions page.
  */
 var AboutPermissions = {
   /**
    * Number of sites to return from the places database.
--- a/browser/components/preferences/in-content/applications.js
+++ b/browser/components/preferences/in-content/applications.js
@@ -398,17 +398,17 @@ HandlerInfoWrapper.prototype = {
                                           this.type,
                                           false);
   },
 
   enablePluginType: function() {
     var disabledPluginTypes = this._getDisabledPluginTypes();
 
     var type = this.type;
-    disabledPluginTypes = disabledPluginTypes.filter(function(v) v != type);
+    disabledPluginTypes = disabledPluginTypes.filter(v => v != type);
 
     this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
                               disabledPluginTypes.join(","));
 
     // Update the category manager so existing browser windows update.
     this._categoryMgr.
       addCategoryEntry("Gecko-Content-Viewers",
                        this.type,
@@ -1544,17 +1544,17 @@ var gApplicationsPane = {
         menu.selectedItem = internalMenuItem;
         break;
       case Ci.nsIHandlerInfo.useSystemDefault:
         menu.selectedItem = defaultMenuItem;
         break;
       case Ci.nsIHandlerInfo.useHelperApp:
         if (preferredApp)
           menu.selectedItem = 
-            possibleAppMenuItems.filter(function(v) v.handlerApp.equals(preferredApp))[0];
+            possibleAppMenuItems.filter(v => v.handlerApp.equals(preferredApp))[0];
         break;
       case kActionUsePlugin:
         menu.selectedItem = pluginMenuItem;
         break;
       case Ci.nsIHandlerInfo.saveToDisk:
         menu.selectedItem = saveMenuItem;
         break;
     }
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -293,17 +293,19 @@ var gMainPane = {
    * Sets the home page to the current displayed page (or frontmost tab, if the
    * most recent browser window contains multiple tabs), updating preference
    * window UI to reflect this.
    */
   setHomePageToCurrent: function ()
   {
     let homePage = document.getElementById("browser.startup.homepage");
     let tabs = this._getTabsForHomePage();
-    function getTabURI(t) t.linkedBrowser.currentURI.spec;
+    function getTabURI(t) {
+      return t.linkedBrowser.currentURI.spec;
+    }
 
     // FIXME Bug 244192: using dangerous "|" joiner!
     if (tabs.length)
       homePage.value = tabs.map(getTabURI).join("|");
   },
 
   /**
    * Displays a dialog in which the user can select a bookmark to use as home
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -174,18 +174,17 @@ var gPrivacyPane = {
   },
 
   /**
    * Initialize the history mode menulist based on the privacy preferences
    */
   initializeHistoryMode: function PPP_initializeHistoryMode()
   {
     let mode;
-    let getVal = function (aPref)
-      document.getElementById(aPref).value;
+    let getVal = aPref => document.getElementById(aPref).value;
 
     if (this._checkDefaultValues(this.prefsForDefault)) {
       if (getVal("browser.privatebrowsing.autostart"))
         mode = "dontremember";
       else
         mode = "remember";
     }
     else
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -313,17 +313,17 @@ function onDragEngineStart(event) {
 function EngineStore() {
   let pref = document.getElementById("browser.search.hiddenOneOffs").value;
   this.hiddenList = pref ? pref.split(",") : [];
 
   this._engines = Services.search.getVisibleEngines().map(this._cloneEngine, this);
   this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine, this);
 
   // check if we need to disable the restore defaults button
-  var someHidden = this._defaultEngines.some(function (e) e.hidden);
+  var someHidden = this._defaultEngines.some(e => e.hidden);
   gSearchPane.showRestoreDefaults(someHidden);
 }
 EngineStore.prototype = {
   _engines: null,
   _defaultEngines: null,
 
   get engines() {
     return this._engines;
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -401,23 +401,27 @@ var gSyncPane = {
       }).then(data => {
         if (data && profileInfoEnabled) {
           if (data.displayName) {
             fxaEmailAddress1Label.hidden = true;
             displayNameLabel.hidden = false;
             displayNameLabel.textContent = data.displayName;
           }
           if (data.avatar) {
-            // Make sure the image is available before displaying it,
-            // as we don't want to overwrite the default profile image
-            // with a broken/unavailable image
+            let bgImage = "url(\"" + data.avatar + "\")";
+            let profileImageElement = document.getElementById("fxaProfileImage");
+            profileImageElement.style.listStyleImage = bgImage;
+
             let img = new Image();
-            img.onload = () => {
-              let bgImage = "url('" + data.avatar + "')";
-              document.getElementById("fxaProfileImage").style.listStyleImage = bgImage;
+            img.onerror = () => {
+              // Clear the image if it has trouble loading. Since this callback is asynchronous
+              // we check to make sure the image is still the same before we clear it.
+              if (profileImageElement.style.listStyleImage === bgImage) {
+                profileImageElement.style.removeProperty("list-style-image");
+              }
             };
             img.src = data.avatar;
           }
         }
       }, err => {
         FxAccountsCommon.log.error(err);
       }).catch(err => {
         // If we get here something's really busted
--- a/browser/components/preferences/translation.js
+++ b/browser/components/preferences/translation.js
@@ -20,58 +20,82 @@ const kLanguagesPref = "browser.translat
 function Tree(aId, aData)
 {
   this._data = aData;
   this._tree = document.getElementById(aId);
   this._tree.view = this;
 }
 
 Tree.prototype = {
-  get boxObject() this._tree.treeBoxObject,
-  get isEmpty() !this._data.length,
-  get hasSelection() this.selection.count > 0,
+  get boxObject() {
+    return this._tree.treeBoxObject;
+  },
+  get isEmpty() {
+    return !this._data.length;
+  },
+  get hasSelection() {
+    return this.selection.count > 0;
+  },
   getSelectedItems: function() {
     let result = [];
 
     let rc = this.selection.getRangeCount();
     for (let i = 0; i < rc; ++i) {
       let min = {}, max = {};
       this.selection.getRangeAt(i, min, max);
       for (let j = min.value; j <= max.value; ++j)
         result.push(this._data[j]);
     }
 
     return result;
   },
 
   // nsITreeView implementation
-  get rowCount() this._data.length,
-  getCellText: function (aRow, aColumn) this._data[aRow],
-  isSeparator: function(aIndex) false,
-  isSorted: function() false,
-  isContainer: function(aIndex) false,
+  get rowCount() {
+    return this._data.length;
+  },
+  getCellText: function (aRow, aColumn) {
+    return this._data[aRow];
+  },
+  isSeparator: function(aIndex) {
+    return false;
+  },
+  isSorted: function() {
+    return false;
+  },
+  isContainer: function(aIndex) {
+    return false;
+  },
   setTree: function(aTree) {},
   getImageSrc: function(aRow, aColumn) {},
   getProgressMode: function(aRow, aColumn) {},
   getCellValue: function(aRow, aColumn) {},
   cycleHeader: function(column) {},
-  getRowProperties: function(row) "",
-  getColumnProperties: function(column) "",
-  getCellProperties: function(row, column) "",
+  getRowProperties: function(row) {
+    return "";
+  },
+  getColumnProperties: function(column) {
+    return "";
+  },
+  getCellProperties: function(row, column) {
+    return "";
+  },
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView])
 };
 
 function Lang(aCode)
 {
   this.langCode = aCode;
   this._label = gLangBundle.GetStringFromName(aCode);
 }
 
 Lang.prototype = {
-  toString: function() this._label
+  toString: function() {
+    return this._label;
+  }
 }
 
 var gTranslationExceptions = {
   onLoad: function() {
     if (this._siteTree) {
       // Re-using an open dialog, clear the old observers.
       this.uninit();
     }
--- a/browser/components/privatebrowsing/test/browser/browser.ini
+++ b/browser/components/privatebrowsing/test/browser/browser.ini
@@ -44,8 +44,9 @@ tags = trackingprotection
 [browser_privatebrowsing_ui.js]
 [browser_privatebrowsing_urlbarfocus.js]
 [browser_privatebrowsing_windowtitle.js]
 skip-if = e10s
 [browser_privatebrowsing_zoom.js]
 skip-if = e10s
 [browser_privatebrowsing_zoomrestore.js]
 skip-if = e10s
+[browser_privatebrowsing_newtab_from_popup.js]
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
@@ -39,17 +39,17 @@ function getFile(downloadLastDir, aURI) 
 
 function setFile(downloadLastDir, aURI, aValue) {
   downloadLastDir.setFile(aURI, aValue);
   executeSoon(moveAlong);
 }
 
 function clearHistoryAndWait() {
   clearHistory();
-  executeSoon(function() executeSoon(moveAlong));
+  executeSoon(() => executeSoon(moveAlong));
 }
 
 /*
  * ===================
  * Function with tests
  * ===================
  */
 
@@ -68,17 +68,17 @@ function runTest() {
   let uri2 = Services.io.newURI("http://test2.com/", null, null);
   let uri3 = Services.io.newURI("http://test3.com/", null, null);
   let uri4 = Services.io.newURI("http://test4.com/", null, null);
 
   // cleanup functions registration
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("browser.download.lastDir.savePerSite");
     Services.prefs.clearUserPref("browser.download.lastDir");
-    [dir1, dir2, dir3].forEach(function(dir) dir.remove(true));
+    [dir1, dir2, dir3].forEach(dir => dir.remove(true));
     win.close();
     pbWin.close();
   });
 
   function checkDownloadLastDir(gDownloadLastDir, aLastDir) {
     is(gDownloadLastDir.file.path, aLastDir.path,
        "gDownloadLastDir should point to the expected last directory");
     getFile(gDownloadLastDir, uri1);
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
@@ -28,17 +28,17 @@ function test() {
   let dir3 = newDirectory();
   let file1 = newFileInDirectory(dir1);
   let file2 = newFileInDirectory(dir2);
   let file3 = newFileInDirectory(dir3);
 
   // cleanup functions registration
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("browser.download.lastDir");
-    [dir1, dir2, dir3].forEach(function(dir) dir.remove(true));
+    [dir1, dir2, dir3].forEach(dir => dir.remove(true));
     MockFilePicker.cleanup();
   });
   prefs.setComplexValue("lastDir", Ci.nsIFile, tmpDir);
 
   function testOnWindow(aPrivate, aCallback) {
     whenNewWindowLoaded({private: aPrivate}, function(win) {
       let gDownloadLastDir = new DownloadLastDir(win);
       aCallback(win, gDownloadLastDir);
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -23,23 +23,23 @@ function test() {
   let dir3 = newDirectory();
   let file1 = newFileInDirectory(dir1);
   let file2 = newFileInDirectory(dir2);
   let file3 = newFileInDirectory(dir3);
 
   // cleanup function registration
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("browser.download.lastDir");
-    [dir1, dir2, dir3].forEach(function(dir) dir.remove(true));
+    [dir1, dir2, dir3].forEach(dir => dir.remove(true));
     MockFilePicker.cleanup();
     validateFileName = validateFileNameToRestore;
   });
 
   // Overwrite validateFileName to validate everything
-  validateFileName = function(foo) foo;
+  validateFileName = foo => foo;
 
   let params = {
     fileInfo: new FileInfo("test.txt", "test.txt", "test", "txt", "http://mozilla.org/test.txt"),
     contentType: "text/plain",
     saveMode: SAVEMODE_FILEONLY,
     saveAsType: kSaveAsType_Complete,
     file: null
   };
new file mode 100644
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js
@@ -0,0 +1,61 @@
+/**
+ * Tests that a popup window in private browsing window opens
+ * new tab links in the original private browsing window as
+ * new tabs.
+ *
+ * This is a regression test for bug 1202634.
+ */
+
+// We're able to sidestep some quote-escaping issues when
+// nesting data URI's by encoding the second data URI in
+// base64.
+const POPUP_BODY_BASE64 = btoa(`<a href="http://example.com/" target="_blank"
+                                   id="second">
+                                  Now click this
+                                </a>`);
+const POPUP_LINK = `data:text/html;charset=utf-8;base64,${POPUP_BODY_BASE64}`;
+const WINDOW_BODY = `data:text/html,
+                     <a href="%23" id="first"
+                        onclick="window.open('${POPUP_LINK}', '_blank',
+                                             'width=630,height=500')">
+                       First click this.
+                     </a>`;
+
+add_task(function* test_private_popup_window_opens_private_tabs() {
+  let privWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+  // Sanity check - this browser better be private.
+  ok(PrivateBrowsingUtils.isWindowPrivate(privWin),
+     "Could not open a private browsing window.");
+
+  // First, open a private browsing window, and load our
+  // testing page.
+  let privBrowser = privWin.gBrowser.selectedBrowser;
+  yield BrowserTestUtils.loadURI(privBrowser, WINDOW_BODY);
+  yield BrowserTestUtils.browserLoaded(privBrowser);
+
+  // Next, click on the link in the testing page, and ensure
+  // that a private popup window is opened.
+  let openedPromise = BrowserTestUtils.waitForNewWindow();
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#first", {}, privBrowser);
+  let popupWin = yield openedPromise;
+  ok(PrivateBrowsingUtils.isWindowPrivate(popupWin),
+     "Popup window was not private.");
+
+  // Now click on the link in the popup, and ensure that a new
+  // tab is opened in the original private browsing window.
+  let newTabPromise = BrowserTestUtils.waitForNewTab(privWin.gBrowser);
+  let popupBrowser = popupWin.gBrowser.selectedBrowser;
+  yield BrowserTestUtils.browserLoaded(popupBrowser);
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#second", {}, popupBrowser);
+  let newPrivTab = yield newTabPromise;
+
+  // Ensure that the newly created tab's browser is private.
+  ok(PrivateBrowsingUtils.isBrowserPrivate(newPrivTab.linkedBrowser),
+     "Newly opened tab should be private.");
+
+  // Clean up
+  yield BrowserTestUtils.removeTab(newPrivTab);
+  yield BrowserTestUtils.closeWindow(popupWin);
+  yield BrowserTestUtils.closeWindow(privWin);
+});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -46,17 +46,17 @@ add_task(function* test() {
     uri: TEST_URI,
     title: TITLE_2,
     visits: [{
       visitDate: Date.now() * 1000,
       transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
     }]
   };
   PlacesUtils.asyncHistory.updatePlaces(place, {
-    handleError: function () ok(false, "Unexpected error in adding visit."),
+    handleError: () => ok(false, "Unexpected error in adding visit."),
     handleResult: function () { },
     handleCompletion: function () {}
   });
 
   yield waitForTitleChanged();
   is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
 
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private:true});
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
@@ -39,54 +39,54 @@ function test() {
   }
 
   let windowCache = [];
   function cacheWindow(w) {
     windowCache.push(w);
     return w;
   }
   function closeCachedWindows () {
-    windowCache.forEach(function(w) w.close());
+    windowCache.forEach(w => w.close());
   }
 
   // Part 1: NON PRIVATE WINDOW -> PRIVATE WINDOW
   openWindow(window, {}, 1).
     then(cacheWindow).
     then(openSidebar).
-    then(function(win) openWindow(win, { private: true })).
+    then(win => openWindow(win, { private: true })).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
       is(sidebarBox.hidden, true, 'Opening a private window from reg window does not open the sidebar');
     }).
     // Part 2: NON PRIVATE WINDOW -> NON PRIVATE WINDOW
-    then(function() openWindow(window)).
+    then(() => openWindow(window)).
     then(cacheWindow).
     then(openSidebar).
-    then(function(win) openWindow(win)).
+    then(win => openWindow(win)).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
       is(sidebarBox.hidden, false, 'Opening a reg window from reg window does open the sidebar');
     }).
     // Part 3: PRIVATE WINDOW -> NON PRIVATE WINDOW
-    then(function() openWindow(window, { private: true })).
+    then(() => openWindow(window, { private: true })).
     then(cacheWindow).
     then(openSidebar).
-    then(function(win) openWindow(win)).
+    then(win => openWindow(win)).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
       is(sidebarBox.hidden, true, 'Opening a reg window from a private window does not open the sidebar');
     }).
     // Part 4: PRIVATE WINDOW -> PRIVATE WINDOW
-    then(function() openWindow(window, { private: true })).
+    then(() => openWindow(window, { private: true })).
     then(cacheWindow).
     then(openSidebar).
-    then(function(win) openWindow(win, { private: true })).
+    then(win => openWindow(win, { private: true })).
     then(cacheWindow).
     then(function({ document }) {
       let sidebarBox = document.getElementById("sidebar-box");
       is(sidebarBox.hidden, false, 'Opening a private window from private window does open the sidebar');
     }).
     then(closeCachedWindows).
     then(finish);
 }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js
@@ -6,17 +6,17 @@
 // adjusted based on whether the window is a private window.
 
 var windowsToClose = [];
 function testOnWindow(options, callback) {
   var win = OpenBrowserWindow(options);
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad, false);
     windowsToClose.push(win);
-    executeSoon(function() callback(win));
+    executeSoon(() => callback(win));
   }, false);
 }
 
 registerCleanupFunction(function() {
   windowsToClose.forEach(function(win) {
     win.close();
   });
 });
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -53,17 +53,17 @@ function test() {
   };
 
   function testOnWindow(aOptions, aCallback) {
     whenNewWindowLoaded(aOptions, function(aWin) {
       windowsToClose.push(aWin);
       // execute should only be called when need, like when you are opening
       // web pages on the test. If calling executeSoon() is not necesary, then
       // call whenNewWindowLoaded() instead of testOnWindow() on your test.
-      executeSoon(function() aCallback(aWin));
+      executeSoon(() => aCallback(aWin));
     });
   };
 
    // this function is called after calling finish() on the test.
   registerCleanupFunction(function() {
     windowsToClose.forEach(function(aWin) {
       aWin.close();
     });
--- a/browser/components/privatebrowsing/test/browser/head.js
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -52,12 +52,12 @@ function newFileInDirectory(aDir) {
 function clearHistory() {
   // simulate clearing the private data
   Services.obs.notifyObservers(null, "browser:purge-session-history", "");
 }
 
 function _initTest() {
   // Don't use about:home as the homepage for new windows
   Services.prefs.setIntPref("browser.startup.page", 0);
-  registerCleanupFunction(function() Services.prefs.clearUserPref("browser.startup.page"));
+  registerCleanupFunction(() => Services.prefs.clearUserPref("browser.startup.page"));
 }
 
 _initTest();
--- a/browser/components/search/test/browser_private_search_perwindowpb.js
+++ b/browser/components/search/test/browser_private_search_perwindowpb.js
@@ -1,16 +1,16 @@
 // This test performs a search in a public window, then a different
 // search in a private window, and then checks in the public window
 // whether there is an autocomplete entry for the private search.
 
 function test() {
   // Don't use about:home as the homepage for new windows
   Services.prefs.setIntPref("browser.startup.page", 0);
-  registerCleanupFunction(function() Services.prefs.clearUserPref("browser.startup.page"));
+  registerCleanupFunction(() => Services.prefs.clearUserPref("browser.startup.page"));
 
   waitForExplicitFinish();
 
   let engineURL =
     "http://mochi.test:8888/browser/browser/components/search/test/";
   let windowsToClose = [];
   registerCleanupFunction(function() {
     let engine = Services.search.getEngineByName("Bug 426329");
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3307,18 +3307,18 @@ var SessionStoreInternal = {
   /**
    * whether the user wants to load any other page at startup
    * (except the homepage) - needed for determining whether to overwrite the current tabs
    * C.f.: nsBrowserContentHandler's defaultArgs implementation.
    * @returns bool
    */
   _isCmdLineEmpty: function ssi_isCmdLineEmpty(aWindow, aState) {
     var pinnedOnly = aState.windows &&
-                     aState.windows.every(function (win)
-                       win.tabs.every(function (tab) tab.pinned));
+                     aState.windows.every(win =>
+                       win.tabs.every(tab => tab.pinned));
 
     let hasFirstArgument = aWindow.arguments && aWindow.arguments[0];
     if (!pinnedOnly) {
       let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
                         getService(Ci.nsIBrowserHandler).defaultArgs;
       if (aWindow.arguments &&
           aWindow.arguments[0] &&
           aWindow.arguments[0] == defaultArgs)
--- a/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js
+++ b/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js
@@ -49,32 +49,32 @@ function test() {
     let node = getElementByXPath(aTab, aQuery);
     if (typeof aValue == "string")
       node.value = aValue;
     else if (typeof aValue == "boolean")
       node.checked = aValue;
     else if (typeof aValue == "number")
       node.selectedIndex = aValue;
     else
-      Array.forEach(node.options, function(aOpt, aIx)
+      Array.forEach(node.options, (aOpt, aIx) =>
         (aOpt.selected = aValue.indexOf(aIx) > -1));
   }
 
   function compareFormValue(aTab, aQuery, aValue) {
     let node = getElementByXPath(aTab, aQuery);
     if (!node)
       return false;
     if (node instanceof Ci.nsIDOMHTMLInputElement)
       return aValue == (node.type == "checkbox" || node.type == "radio" ?
                        node.checked : node.value);
     if (node instanceof Ci.nsIDOMHTMLTextAreaElement)
       return aValue == node.value;
     if (!node.multiple)
       return aValue == node.selectedIndex;
-    return Array.every(node.options, function(aOpt, aIx)
+    return Array.every(node.options, (aOpt, aIx) =>
             (aValue.indexOf(aIx) > -1) == aOpt.selected);
   }
 
   //////////////////////////////////////////////////////////////////
   // Test (B) : Session data restoration between windows          //
   //////////////////////////////////////////////////////////////////
 
   let rootDir = getRootDirectory(gTestPath);
@@ -112,17 +112,17 @@ function test() {
       // public session, close tab: (A)
       aWin.gBrowser.removeTab(tab_A);
 
       // verify that closedTabCount increased
       ok(ss.getClosedTabCount(aWin) > count,
          "getClosedTabCount has increased after closing a tab");
 
       // verify tab: (A), in undo list
-      let tab_A_restored = test(function() ss.undoCloseTab(aWin, 0));
+      let tab_A_restored = test(() => ss.undoCloseTab(aWin, 0));
       ok(tab_A_restored, "a tab is in undo list");
       promiseTabRestored(tab_A_restored).then(() => {
         is(testURL, tab_A_restored.linkedBrowser.currentURI.spec,
            "it's the same tab that we expect");
         aWin.gBrowser.removeTab(tab_A_restored);
 
         whenNewWindowLoaded({ private: true }, function(aWin) {
           windowsToClose.push(aWin);
--- a/browser/components/sessionstore/test/browser_345898.js
+++ b/browser/components/sessionstore/test/browser_345898.js
@@ -12,33 +12,33 @@ function test() {
     }
     catch (ex) {
       return ex.name == "NS_ERROR_ILLEGAL_VALUE" ||
              ex.name == "NS_ERROR_FAILURE";
     }
   }
 
   // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
-  ok(test(function() ss.getWindowState({})),
+  ok(test(() => ss.getWindowState({})),
      "Invalid window for getWindowState throws");
-  ok(test(function() ss.setWindowState({}, "", false)),
+  ok(test(() => ss.setWindowState({}, "", false)),
      "Invalid window for setWindowState throws");
-  ok(test(function() ss.getTabState({})),
+  ok(test(() => ss.getTabState({})),
      "Invalid tab for getTabState throws");
-  ok(test(function() ss.setTabState({}, "{}")),
+  ok(test(() => ss.setTabState({}, "{}")),
      "Invalid tab state for setTabState throws");
-  ok(test(function() ss.setTabState({}, JSON.stringify({ entries: [] }))),
+  ok(test(() => ss.setTabState({}, JSON.stringify({ entries: [] }))),
      "Invalid tab for setTabState throws");
-  ok(test(function() ss.duplicateTab({}, {})),
+  ok(test(() => ss.duplicateTab({}, {})),
      "Invalid tab for duplicateTab throws");
-  ok(test(function() ss.duplicateTab({}, gBrowser.selectedTab)),
+  ok(test(() => ss.duplicateTab({}, gBrowser.selectedTab)),
      "Invalid window for duplicateTab throws");
-  ok(test(function() ss.getClosedTabData({})),
+  ok(test(() => ss.getClosedTabData({})),
      "Invalid window for getClosedTabData throws");
-  ok(test(function() ss.undoCloseTab({}, 0)),
+  ok(test(() => ss.undoCloseTab({}, 0)),
      "Invalid window for undoCloseTab throws");
-  ok(test(function() ss.undoCloseTab(window, -1)),
+  ok(test(() => ss.undoCloseTab(window, -1)),
      "Invalid index for undoCloseTab throws");
-  ok(test(function() ss.getWindowValue({}, "")),
+  ok(test(() => ss.getWindowValue({}, "")),
      "Invalid window for getWindowValue throws");
-  ok(test(function() ss.setWindowValue({}, "", "")),
+  ok(test(() => ss.setWindowValue({}, "", "")),
      "Invalid window for setWindowValue throws");
 }
--- a/browser/components/sessionstore/test/browser_350525.js
+++ b/browser/components/sessionstore/test/browser_350525.js
@@ -13,52 +13,52 @@ add_task(function* () {
 
   ////////////////////////////
   // setWindowValue, et al. //
   ////////////////////////////
   let key = "Unique name: " + Date.now();
   let value = "Unique value: " + Math.random();
 
   // test adding
-  ok(test(function() ss.setWindowValue(window, key, value)), "set a window value");
+  ok(test(() => ss.setWindowValue(window, key, value)), "set a window value");
 
   // test retrieving
   is(ss.getWindowValue(window, key), value, "stored window value matches original");
 
   // test deleting
-  ok(test(function() ss.deleteWindowValue(window, key)), "delete the window value");
+  ok(test(() => ss.deleteWindowValue(window, key)), "delete the window value");
 
   // value should not exist post-delete
   is(ss.getWindowValue(window, key), "", "window value was deleted");
 
   // test deleting a non-existent value
-  ok(test(function() ss.deleteWindowValue(window, key)), "delete non-existent window value");
+  ok(test(() => ss.deleteWindowValue(window, key)), "delete non-existent window value");
 
   /////////////////////////
   // setTabValue, et al. //
   /////////////////////////
   key = "Unique name: " + Math.random();
   value = "Unique value: " + Date.now();
   let tab = gBrowser.addTab();
   tab.linkedBrowser.stop();
 
   // test adding
-  ok(test(function() ss.setTabValue(tab, key, value)), "store a tab value");
+  ok(test(() => ss.setTabValue(tab, key, value)), "store a tab value");
 
   // test retrieving
   is(ss.getTabValue(tab, key), value, "stored tab value match original");
 
   // test deleting
-  ok(test(function() ss.deleteTabValue(tab, key)), "delete the tab value");
+  ok(test(() => ss.deleteTabValue(tab, key)), "delete the tab value");
 
   // value should not exist post-delete
   is(ss.getTabValue(tab, key), "", "tab value was deleted");
 
   // test deleting a non-existent value
-  ok(test(function() ss.deleteTabValue(tab, key)), "delete non-existent tab value");
+  ok(test(() => ss.deleteTabValue(tab, key)), "delete non-existent tab value");
 
   // clean up
   yield promiseRemoveTab(tab);
 
   /////////////////////////////////////
   // getClosedTabCount, undoCloseTab //
   /////////////////////////////////////
 
@@ -80,17 +80,17 @@ add_task(function* () {
   // remove tab
   yield promiseRemoveTab(tab);
 
   // getClosedTabCount
   let newcount = ss.getClosedTabCount(window);
   ok(newcount > count, "after closing a tab, getClosedTabCount has been incremented");
 
   // undoCloseTab
-  tab = test(function() ss.undoCloseTab(window, 0));
+  tab = test(() => ss.undoCloseTab(window, 0));
   ok(tab, "undoCloseTab doesn't throw")
 
   yield promiseTabRestored(tab);
   is(tab.linkedBrowser.currentURI.spec, testURL, "correct tab was reopened");
 
   // clean up
   gBrowser.removeTab(tab);
 });
--- a/browser/components/sessionstore/test/browser_354894_perwindowpb.js
+++ b/browser/components/sessionstore/test/browser_354894_perwindowpb.js
@@ -431,17 +431,17 @@ function test() {
         ok(!newWin.closed, "First close attempt denied");
         if (!newWin.closed) {
           newWin.BrowserTryToCloseWindow();
           ok(newWin.closed, "Second close attempt granted");
         }
       }
 
       if (iteration < NOTIFICATIONS_EXPECTED - 1) {
-        executeSoon(function() testMacNotifications(nextFn, ++iteration));
+        executeSoon(() => testMacNotifications(nextFn, ++iteration));
       }
       else {
         executeSoon(nextFn);
       }
     });
   }
 
   // Execution starts here
--- a/browser/components/sessionstore/test/browser_394759_purge.js
+++ b/browser/components/sessionstore/test/browser_394759_purge.js
@@ -12,21 +12,23 @@ function waitForClearHistory(aCallback) 
     }
   };
   Services.obs.addObserver(observer, "browser:purge-domain-data", false);
 }
 
 function test() {
   waitForExplicitFinish();
   // utility functions
-  function countClosedTabsByTitle(aClosedTabList, aTitle)
-    aClosedTabList.filter(function (aData) aData.title == aTitle).length;
+  function countClosedTabsByTitle(aClosedTabList, aTitle) {
+    return aClosedTabList.filter(aData => aData.title == aTitle).length;
+  }
 
-  function countOpenTabsByTitle(aOpenTabList, aTitle)
-    aOpenTabList.filter(function (aData) aData.entries.some(function (aEntry) aEntry.title == aTitle)).length
+  function countOpenTabsByTitle(aOpenTabList, aTitle) {
+    return aOpenTabList.filter(aData => aData.entries.some(aEntry => aEntry.title == aTitle)).length;
+  }
 
   // backup old state
   let oldState = ss.getBrowserState();
   let oldState_wins = JSON.parse(oldState).windows.length;
   if (oldState_wins != 1)
     ok(false, "oldState in test_purge has " + oldState_wins + " windows instead of 1");
 
   // create a new state for testing
--- a/browser/components/sessionstore/test/browser_461634.js
+++ b/browser/components/sessionstore/test/browser_461634.js
@@ -11,18 +11,19 @@ function test() {
   let test_state = { windows: [{ "tabs": [{ "entries": [] }], _closedTabs: [
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: REMEMBER },
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
     { state: { entries: [{ url: "http://www.example.net/" }] }, title: REMEMBER },
   ] }] };
   let remember_count = 2;
 
-  function countByTitle(aClosedTabList, aTitle)
-    aClosedTabList.filter(function(aData) aData.title == aTitle).length;
+  function countByTitle(aClosedTabList, aTitle) {
+    return aClosedTabList.filter(aData => aData.title == aTitle).length;
+  }
 
   function testForError(aFunction) {
     try {
       aFunction();
       return false;
     }
     catch (ex) {
       return ex.name == "NS_ERROR_ILLEGAL_VALUE";
@@ -41,21 +42,21 @@ function test() {
        "Closed tab list has the expected length");
     is(countByTitle(closedTabs, FORGET),
        test_state.windows[0]._closedTabs.length - remember_count,
        "The correct amout of tabs are to be forgotten");
     is(countByTitle(closedTabs, REMEMBER), remember_count,
        "Everything is set up.");
 
     // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
-    ok(testForError(function() ss.forgetClosedTab({}, 0)),
+    ok(testForError(() => ss.forgetClosedTab({}, 0)),
        "Invalid window for forgetClosedTab throws");
-    ok(testForError(function() ss.forgetClosedTab(newWin, -1)),
+    ok(testForError(() => ss.forgetClosedTab(newWin, -1)),
        "Invalid tab for forgetClosedTab throws");
-    ok(testForError(function() ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
+    ok(testForError(() => ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
        "Invalid tab for forgetClosedTab throws");
 
     // Remove third tab, then first tab
     ss.forgetClosedTab(newWin, 2);
     ss.forgetClosedTab(newWin, null);
 
     closedTabs = JSON.parse(ss.getClosedTabData(newWin));
     is(closedTabs.length, remember_count,
--- a/browser/components/sessionstore/test/browser_464199.js
+++ b/browser/components/sessionstore/test/browser_464199.js
@@ -42,18 +42,19 @@ function test() {
     { state: { entries: [{ url: "http://www.example.org/form",
                            formdata: { id: { "url": "http://www.example.net/" } }
                          }] }, title: REMEMBER },
     { state: { entries: [{ url: "http://www.example.org/form" }],
                extData: { "setTabValue": "http://example.net:80" } }, title: REMEMBER }
   ] }] };
   let remember_count = 5;
 
-  function countByTitle(aClosedTabList, aTitle)
-    aClosedTabList.filter(function(aData) aData.title == aTitle).length;
+  function countByTitle(aClosedTabList, aTitle) {
+    return aClosedTabList.filter(aData => aData.title == aTitle).length;
+  }
 
   // open a window and add the above closed tab list
   let newWin = openDialog(location, "", "chrome,all,dialog=no");
   promiseWindowLoaded(newWin).then(() => {
     gPrefService.setIntPref("browser.sessionstore.max_tabs_undo",
                             test_state.windows[0]._closedTabs.length);
     ss.setWindowState(newWin, JSON.stringify(test_state), true);
 
--- a/browser/components/sessionstore/test/browser_491577.js
+++ b/browser/components/sessionstore/test/browser_491577.js
@@ -60,18 +60,19 @@ function test() {
             title: "title"
           }
         ]
       }
     ]
   };
   let remember_count = 1;
 
-  function countByTitle(aClosedWindowList, aTitle)
-    aClosedWindowList.filter(function(aData) aData.title == aTitle).length;
+  function countByTitle(aClosedWindowList, aTitle) {
+    return aClosedWindowList.filter(aData => aData.title == aTitle).length;
+  }
 
   function testForError(aFunction) {
     try {
       aFunction();
       return false;
     }
     catch (ex) {
       return ex.name == "NS_ERROR_ILLEGAL_VALUE";
@@ -90,19 +91,19 @@ function test() {
        "Closed window list has the expected length");
     is(countByTitle(closedWindows, FORGET),
        test_state._closedWindows.length - remember_count,
        "The correct amount of windows are to be forgotten");
     is(countByTitle(closedWindows, REMEMBER), remember_count,
        "Everything is set up.");
 
     // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
-    ok(testForError(function() ss.forgetClosedWindow(-1)),
+    ok(testForError(() => ss.forgetClosedWindow(-1)),
        "Invalid window for forgetClosedWindow throws");
-    ok(testForError(function() ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
+    ok(testForError(() => ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
        "Invalid window for forgetClosedWindow throws");
 
     // Remove third window, then first window
     ss.forgetClosedWindow(2);
     ss.forgetClosedWindow(null);
 
     closedWindows = JSON.parse(ss.getClosedWindowData());
     is(closedWindows.length, remember_count,
--- a/browser/components/sessionstore/test/browser_588426.js
+++ b/browser/components/sessionstore/test/browser_588426.js
@@ -5,17 +5,17 @@ function test() {
   let state = { windows: [{ tabs: [
       {entries: [{url: "about:mozilla"}], hidden: true},
       {entries: [{url: "about:rights"}], hidden: true}
   ] }] };
 
   waitForExplicitFinish();
 
   newWindowWithState(state, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     is(win.gBrowser.tabs.length, 2, "two tabs were restored");
     is(win.gBrowser.visibleTabs.length, 1, "one tab is visible");
 
     let tab = win.gBrowser.visibleTabs[0];
     is(tab.linkedBrowser.currentURI.spec, "about:mozilla", "visible tab is about:mozilla");
 
     finish();
--- a/browser/components/sessionstore/test/browser_589246.js
+++ b/browser/components/sessionstore/test/browser_589246.js
@@ -35,17 +35,17 @@ var tests = [];
 function checkOSX34Generator(num) {
   return function(aPreviousState, aCurState) {
     // In here, we should have restored the pinned tab, so only the unpinned tab
     // should be in aCurState. So let's shape our expectations.
     let expectedState = JSON.parse(aPreviousState);
     expectedState[0].tabs.shift();
     // size attributes are stripped out in _prepDataForDeferredRestore in nsSessionStore.
     // This isn't the best approach, but neither is comparing JSON strings
-    WINDOW_ATTRIBUTES.forEach(function (attr) delete expectedState[0][attr]);
+    WINDOW_ATTRIBUTES.forEach(attr => delete expectedState[0][attr]);
 
     is(aCurState, JSON.stringify(expectedState),
        "test #" + num + ": closedWindowState is as expected");
   };
 }
 function checkNoWindowsGenerator(num) {
   return function(aPreviousState, aCurState) {
     is(aCurState, "[]", "test #" + num + ": there should be no closedWindowsLeft");
--- a/browser/components/sessionstore/test/browser_590563.js
+++ b/browser/components/sessionstore/test/browser_590563.js
@@ -12,17 +12,17 @@ function test() {
   };
   let url = "about:sessionrestore";
   let formdata = {id: {sessionData}, url};
   let state = { windows: [{ tabs: [{ entries: [{url}], formdata }] }] };
 
   waitForExplicitFinish();
 
   newWindowWithState(state, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     is(gBrowser.tabs.length, 1, "The total number of tabs should be 1");
     is(gBrowser.visibleTabs.length, 1, "The total number of visible tabs should be 1");
 
     executeSoon(function () {
       waitForFocus(function () {
         middleClickTest(win);
         finish();
--- a/browser/components/sessionstore/test/browser_595601-restore_hidden.js
+++ b/browser/components/sessionstore/test/browser_595601-restore_hidden.js
@@ -96,17 +96,17 @@ var TabsProgressListener = {
   }
 }
 
 // ----------
 function newWindowWithState(state, callback) {
   let opts = "chrome,all,dialog=no,height=800,width=800";
   let win = window.openDialog(getBrowserURL(), "_blank", opts);
 
-  registerCleanupFunction(function () win.close());
+  registerCleanupFunction(() => win.close());
 
   whenWindowLoaded(win, function onWindowLoaded(aWin) {
     TabsProgressListener.init(aWin);
     TabsProgressListener.setCallback(callback);
 
     ss.setWindowState(aWin, JSON.stringify(state), true);
   });
 }
--- a/browser/components/sessionstore/test/browser_599909.js
+++ b/browser/components/sessionstore/test/browser_599909.js
@@ -96,17 +96,19 @@ function checkAutocompleteResults(aExpec
       for (let entry in aExpected) {
         ok(false, "'" + entry + "' not found in autocomplete.");
       }
 
       executeSoon(aCallback);
     },
     setSelectedIndex: function() {},
     get searchCount() { return this.searches.length; },
-    getSearchAt: function(aIndex) this.searches[aIndex],
+    getSearchAt: function(aIndex) {
+      return this.searches[aIndex];
+    },
     QueryInterface: XPCOMUtils.generateQI([
       Ci.nsIAutoCompleteInput,
       Ci.nsIAutoCompletePopup,
     ])
   };
 
   info("Searching open pages.");
   gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
--- a/browser/components/sessionstore/test/browser_659591.js
+++ b/browser/components/sessionstore/test/browser_659591.js
@@ -22,11 +22,11 @@ function test() {
 }
 
 function newWindow(callback) {
   let opts = "chrome,all,dialog=no,height=800,width=800";
   let win = window.openDialog(getBrowserURL(), "_blank", opts);
 
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad, false);
-    executeSoon(function () callback(win));
+    executeSoon(() => callback(win));
   }, false);
 }
--- a/browser/components/sessionstore/test/browser_701377.js
+++ b/browser/components/sessionstore/test/browser_701377.js
@@ -9,17 +9,17 @@ var state = {windows:[{tabs:[
 function test() {
   waitForExplicitFinish();
 
   newWindowWithState(state, function (aWindow) {
     let tab = aWindow.gBrowser.tabs[1];
     ok(tab.hidden, "the second tab is hidden");
 
     let tabShown = false;
-    let tabShowCallback = function () tabShown = true;
+    let tabShowCallback = () => tabShown = true;
     tab.addEventListener("TabShow", tabShowCallback, false);
 
     let tabState = ss.getTabState(tab);
     ss.setTabState(tab, tabState);
 
     tab.removeEventListener("TabShow", tabShowCallback, false);
     ok(tab.hidden && !tabShown, "tab remains hidden");
 
@@ -27,15 +27,15 @@ function test() {
   });
 }
 
 // ----------
 function newWindowWithState(aState, aCallback) {
   let opts = "chrome,all,dialog=no,height=800,width=800";
   let win = window.openDialog(getBrowserURL(), "_blank", opts);
 
-  registerCleanupFunction(function () win.close());
+  registerCleanupFunction(() => win.close());
 
   whenWindowLoaded(win, function onWindowLoaded(aWin) {
     ss.setWindowState(aWin, JSON.stringify(aState), true);
-    executeSoon(function () aCallback(aWin));
+    executeSoon(() => aCallback(aWin));
   });
 }
--- a/browser/components/sessionstore/test/browser_705597.js
+++ b/browser/components/sessionstore/test/browser_705597.js
@@ -49,10 +49,10 @@ function test() {
     });
   });
 }
 
 function whenChildCount(aEntry, aChildCount, aCallback) {
   if (aEntry.childCount == aChildCount)
     aCallback();
   else
-    setTimeout(function () whenChildCount(aEntry, aChildCount, aCallback), 100);
+    setTimeout(() => whenChildCount(aEntry, aChildCount, aCallback), 100);
 }
--- a/browser/components/sessionstore/test/browser_707862.js
+++ b/browser/components/sessionstore/test/browser_707862.js
@@ -52,10 +52,10 @@ function test() {
   // let's add a dummy pass.
   ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
 }
 
 function whenChildCount(aEntry, aChildCount, aCallback) {
   if (aEntry.childCount == aChildCount)
     aCallback();
   else
-    setTimeout(function () whenChildCount(aEntry, aChildCount, aCallback), 100);
+    setTimeout(() => whenChildCount(aEntry, aChildCount, aCallback), 100);
 }
--- a/browser/components/shell/content/setDesktopBackground.js
+++ b/browser/components/shell/content/setDesktopBackground.js
@@ -106,17 +106,17 @@ var gSetBackground = {
   {
     return parseInt(aString.substring(1,3), 16) << 16 |
            parseInt(aString.substring(3,5), 16) << 8 |
            parseInt(aString.substring(5,7), 16);
   },
 
   _rgbToHex: function (aR, aG, aB)
   {
-    return "#" + [aR, aG, aB].map(function(aInt) aInt.toString(16).replace(/^(.)$/, "0$1"))
+    return "#" + [aR, aG, aB].map(aInt => aInt.toString(16).replace(/^(.)$/, "0$1"))
                              .join("").toUpperCase();
   },
 #else
   observe: function (aSubject, aTopic, aData)
   {
     if (aTopic == "shell:desktop-background-changed") {
       document.getElementById("setDesktopBackground").hidden = true;
       document.getElementById("showDesktopPreferences").hidden = false;
--- a/browser/components/shell/test/unit/test_421977.js
+++ b/browser/components/shell/test/unit/test_421977.js
@@ -15,17 +15,17 @@ function colorToHex(aColor) {
   const rMask = 4294901760;
   const gMask = 65280;
   const bMask = 255;
 
   var r = (aColor & rMask) >> 16;
   var g = (aColor & gMask) >> 8;
   var b = (aColor & bMask);
 
-  return "#" + [r, g, b].map(function(aInt)
+  return "#" + [r, g, b].map(aInt =>
                               aInt.toString(16).replace(/^(.)$/, "0$1"))
                              .join("").toUpperCase();
 }
 
 /**
  * Converts a color string in #RRGGBB format to a rgb numerical color value
  *  (r << 16 | g << 8 | b).
  */
--- a/browser/components/tabview/favicons.js
+++ b/browser/components/tabview/favicons.js
@@ -18,17 +18,19 @@ var FavIcons = {
   },
 
   // Lazy getter for pref browser.chrome.favicons.
   get _prefFavicons() {
     delete this._prefFavicons;
     this._prefFavicons = Services.prefs.getBoolPref(this.PREF_CHROME_FAVICONS);
   },
 
-  get defaultFavicon() this._favIconService.defaultFavicon.spec,
+  get defaultFavicon() {
+    return this._favIconService.defaultFavicon.spec;
+  },
 
   init: function FavIcons_init() {
     XPCOMUtils.defineLazyServiceGetter(this, "_favIconService",
       "@mozilla.org/browser/favicon-service;1", "nsIFaviconService");
 
     Services.prefs.addObserver(this.PREF_CHROME_SITE_ICONS, this, false);
     Services.prefs.addObserver(this.PREF_CHROME_FAVICONS, this, false);
   },
--- a/browser/components/tabview/groupitems.js
+++ b/browser/components/tabview/groupitems.js
@@ -1278,17 +1278,17 @@ GroupItem.prototype = Utils.extend(new I
   _freezeItemSize: function GroupItem__freezeItemSize(itemCount) {
     let data = this._frozenItemSizeData;
 
     if (!data.lastItemCount) {
       let self = this;
       data.lastItemCount = itemCount;
 
       // unfreeze item size when tabview is hidden
-      data.onTabViewHidden = function () self._unfreezeItemSize();
+      data.onTabViewHidden = () => self._unfreezeItemSize();
       window.addEventListener('tabviewhidden', data.onTabViewHidden, false);
 
       // we don't need to observe mouse movement when expanded because the
       // tray is closed when we leave it and collapse causes unfreezing
       if (!self.expanded) {
         // unfreeze item size when cursor is moved out of group bounds
         data.onMouseMove = function (e) {
           let cursor = new Point(e.pageX, e.pageY);
@@ -1835,32 +1835,32 @@ GroupItem.prototype = Utils.extend(new I
 
   // ----------
   // Function: reorderTabItemsBasedOnTabOrder
   // Reorders the tabs in a groupItem based on the arrangment of the tabs
   // shown in the tab bar. It does it by sorting the children
   // of the groupItem by the positions of their respective tabs in the
   // tab bar.
   reorderTabItemsBasedOnTabOrder: function GroupItem_reorderTabItemsBasedOnTabOrder() {
-    this._children.sort(function(a,b) a.tab._tPos - b.tab._tPos);
+    this._children.sort((a,b) => a.tab._tPos - b.tab._tPos);
 
     this.arrange({animate: false});
     // this.arrange calls this.save for us
   },
 
   // Function: reorderTabsBasedOnTabItemOrder
   // Reorders the tabs in the tab bar based on the arrangment of the tabs
   // shown in the groupItem.
   reorderTabsBasedOnTabItemOrder: function GroupItem_reorderTabsBasedOnTabItemOrder() {
     let indices;
-    let tabs = this._children.map(function (tabItem) tabItem.tab);
+    let tabs = this._children.map(tabItem => tabItem.tab);
 
     tabs.forEach(function (tab, index) {
       if (!indices)
-        indices = tabs.map(function (tab) tab._tPos);
+        indices = tabs.map(tab => tab._tPos);
 
       let start = index ? indices[index - 1] + 1 : 0;
       let end = index + 1 < indices.length ? indices[index + 1] - 1 : Infinity;
       let targetRange = new Range(start, end);
 
       if (!targetRange.contains(tab._tPos)) {
         gBrowser.moveTabTo(tab, start);
         indices = null;
@@ -2417,17 +2417,17 @@ var GroupItems = {
   // Hides and shows tabs in the tab bar based on the active groupItem
   _updateTabBar: function GroupItems__updateTabBar() {
     if (!window.UI)
       return; // called too soon
 
     Utils.assert(this._activeGroupItem, "There must be something to show in the tab bar!");
 
     let tabItems = this._activeGroupItem._children;
-    gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab));
+    gBrowser.showOnlyTheseTabs(tabItems.map(item => item.tab));
   },
 
   // ----------
   // Function: updateActiveGroupItemAndTabBar
   // Sets active TabItem and GroupItem, and updates tab bar appropriately.
   // Parameters:
   // tabItem - the tab item
   // options - is passed to UI.setActive() directly
--- a/browser/components/tabview/iq.js
+++ b/browser/components/tabview/iq.js
@@ -641,17 +641,19 @@ iQClass.prototype = {
     return this;
   },
 
   // ----------
   // Function: bind
   // Binds the given function to the given event type. Also wraps the function
   // in a try/catch block that does a Utils.log on any errors.
   bind: function iQClass_bind(type, func) {
-    let handler = function(event) func.apply(this, [event]);
+    let handler = function(event) {
+      return func.apply(this, [event]);
+    };
 
     for (let i = 0; this[i] != null; i++) {
       let elem = this[i];
       if (!elem.iQEventData)
         elem.iQEventData = {};
 
       if (!elem.iQEventData[type])
         elem.iQEventData[type] = [];
--- a/browser/components/tabview/modules/utils.jsm
+++ b/browser/components/tabview/modules/utils.jsm
@@ -79,35 +79,43 @@ Rect.prototype = {
   // ----------
   // Function: toString
   // Prints [Rect (left,top,width,height)] for debug use
   toString: function Rect_toString() {
     return "[Rect (" + this.left + "," + this.top + "," +
             this.width + "," + this.height + ")]";
   },
 
-  get right() this.left + this.width,
+  get right() {
+    return this.left + this.width;
+  },
   set right(value) {
     this.width = value - this.left;
   },
 
-  get bottom() this.top + this.height,
+  get bottom() {
+    return this.top + this.height;
+  },
   set bottom(value) {
     this.height = value - this.top;
   },
 
   // ----------
   // Variable: xRange
   // Gives you a new <Range> for the horizontal dimension.
-  get xRange() new Range(this.left, this.right),
+  get xRange() {
+    return new Range(this.left, this.right);
+  },
 
   // ----------
   // Variable: yRange
   // Gives you a new <Range> for the vertical dimension.
-  get yRange() new Range(this.top, this.bottom),
+  get yRange() {
+    return new Range(this.top, this.bottom);
+  },
 
   // ----------
   // Function: intersects
   // Returns true if this rectangle intersects the given <Rect>.
   intersects: function Rect_intersects(rect) {
     return (rect.right > this.left &&
             rect.left < this.right &&
             rect.bottom > this.top &&
@@ -686,17 +694,17 @@ this.Utils = {
     }
     return value;
   },
 
   // ----------
   // Function: merge
   // Merge two array-like objects into the first and return it.
   merge: function Utils_merge(first, second) {
-    Array.forEach(second, function(el) Array.push(first, el));
+    Array.forEach(second, el => Array.push(first, el));
     return first;
   },
 
   // ----------
   // Function: extend
   // Pass several objects in and it will combine them all into the first object and return it.
   extend: function Utils_extend() {
 
--- a/browser/components/tabview/tabview.js
+++ b/browser/components/tabview/tabview.js
@@ -18,18 +18,22 @@ XPCOMUtils.defineLazyGetter(this, "tabvi
   return Services.strings.
     createBundle("chrome://browser/locale/tabview.properties");
 });
 XPCOMUtils.defineLazyGetter(this, "tabbrowserBundle", function() {
   return Services.strings.
     createBundle("chrome://browser/locale/tabbrowser.properties");
 });
 
-function tabviewString(name) tabviewBundle.GetStringFromName('tabview.' + name);
-function tabbrowserString(name) tabbrowserBundle.GetStringFromName(name);
+function tabviewString(name) {
+  return tabviewBundle.GetStringFromName('tabview.' + name);
+}
+function tabbrowserString(name) {
+  return tabbrowserBundle.GetStringFromName(name);
+}
 
 XPCOMUtils.defineLazyGetter(this, "gPrefBranch", function() {
   return Services.prefs.getBranch("browser.panorama.");
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "gPageThumbnails",
   "resource://gre/modules/PageThumbs.jsm", "PageThumbs");
 
@@ -47,17 +51,17 @@ var AllTabs = {
     move:         "TabMove",
     open:         "TabOpen",
     select:       "TabSelect",
     pinned:       "TabPinned",
     unpinned:     "TabUnpinned"
   },
 
   get tabs() {
-    return Array.filter(gBrowser.tabs, function (tab) Utils.isValidXULTab(tab));
+    return Array.filter(gBrowser.tabs, tab => Utils.isValidXULTab(tab));
   },
 
   register: function AllTabs_register(eventName, callback) {
     gBrowser.tabContainer.addEventListener(this._events[eventName], callback, false);
   },
 
   unregister: function AllTabs_unregister(eventName, callback) {
     gBrowser.tabContainer.removeEventListener(this._events[eventName], callback, false);
--- a/browser/components/tabview/test/browser_tabview_bug587503.js
+++ b/browser/components/tabview/test/browser_tabview_bug587503.js
@@ -59,17 +59,17 @@ function test() {
 	closeGroupItem(aGroup, function() { hideTabView(finish) });
       });
     } else {
       closeGroupItem(aGroup, function() { hideTabView(finish) });
     }
   }
 
   function createGroup(win) {
-    registerCleanupFunction(function() win.close());
+    registerCleanupFunction(() => win.close());
     let cw = win.TabView.getContentWindow();
     let group = createGroupItemWithTabs(win, 400, 430, 100, [
       "about:blank#0", "about:blank#1", "about:blank#2", "about:blank#3",
       "about:blank#4", "about:blank#5", "about:blank#6"
     ]);
     let groupSize = group.getChildren().length;
 
     is(cw.GroupItems.groupItems.length, 2, "Validate group count in tab view.");
--- a/browser/components/tabview/test/browser_tabview_bug587990.js
+++ b/browser/components/tabview/test/browser_tabview_bug587990.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   let newTab;
 
   let onLoad = function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     newTab = win.gBrowser.addTab();
 
     let popup = win.document.getElementById("context_tabViewMenuPopup");
     win.TabView.updateContextMenu(newTab, popup);
   };
 
   let onShow = function (win) {
--- a/browser/components/tabview/test/browser_tabview_bug589324.js
+++ b/browser/components/tabview/test/browser_tabview_bug589324.js
@@ -53,17 +53,17 @@ function test() {
   testTabSwitchAfterRestore(function () {
     Services.prefs.setBoolPref("browser.sessionstore.restore_hidden_tabs", true);
     testTabSwitchAfterRestore(finish);
   });
 }
 
 function testTabSwitchAfterRestore(callback) {
   newWindowWithState(state, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let [firstTab, secondTab] = win.gBrowser.tabs;
     is(firstTab.linkedBrowser.currentURI.spec, DUMMY_PAGE_URL,
        "The url of first tab url is dummy_page.html");
 
     // check the hidden state of both tabs.
     ok(firstTab.hidden, "The first tab is hidden");
     ok(!secondTab.hidden, "The second tab is not hidden");
--- a/browser/components/tabview/test/browser_tabview_bug591706.js
+++ b/browser/components/tabview/test/browser_tabview_bug591706.js
@@ -18,30 +18,30 @@ function onTabViewWindowLoaded() {
 
   let contentWindow = document.getElementById("tab-view").contentWindow;
   let [originalTab] = gBrowser.visibleTabs;
 
   // Create a first tab and orphan it
   let firstTab = gBrowser.loadOneTab("about:blank#1", {inBackground: true});
   let firstTabItem = firstTab._tabViewTabItem;
   let currentGroup = contentWindow.GroupItems.getActiveGroupItem();
-  ok(currentGroup.getChildren().some(function(child) child == firstTabItem),"The first tab was made in the current group");
+  ok(currentGroup.getChildren().some(child => child == firstTabItem),"The first tab was made in the current group");
   contentWindow.GroupItems.getActiveGroupItem().remove(firstTabItem);
-  ok(!currentGroup.getChildren().some(function(child) child == firstTabItem),"The first tab was orphaned");
+  ok(!currentGroup.getChildren().some(child => child == firstTabItem),"The first tab was orphaned");
 
   // Create a group and make it active
   let box = new contentWindow.Rect(10, 10, 300, 300);
   let group = new contentWindow.GroupItem([], { bounds: box });
   ok(group.isEmpty(), "This group is empty");
   contentWindow.UI.setActive(group);
   
   // Create a second tab in this new group
   let secondTab = gBrowser.loadOneTab("about:blank#2", {inBackground: true});
   let secondTabItem = secondTab._tabViewTabItem;
-  ok(group.getChildren().some(function(child) child == secondTabItem),"The second tab was made in our new group");
+  ok(group.getChildren().some(child => child == secondTabItem),"The second tab was made in our new group");
   is(group.getChildren().length, 1, "Only one tab in the first group");
   isnot(firstTab.linkedBrowser.currentURI.spec, secondTab.linkedBrowser.currentURI.spec, "The two tabs must have different locations");
 
   // Add the first tab to the group *programmatically*, without specifying a dropPos
   group.add(firstTabItem);
   is(group.getChildren().length, 2, "Two tabs in the group");
 
   is(group.getChildren()[0].tab.linkedBrowser.currentURI.spec, secondTab.linkedBrowser.currentURI.spec, "The second tab was there first");
--- a/browser/components/tabview/test/browser_tabview_bug593283.js
+++ b/browser/components/tabview/test/browser_tabview_bug593283.js
@@ -47,17 +47,17 @@ var state = {
     }, sizemode:"normal"
   }]
 };
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithState(state, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     showTabView(function() {
       let cw = win.TabView.getContentWindow();
       let groupItems = cw.GroupItems.groupItems;
       let groupOne = groupItems[0];
       let groupTwo = groupItems[1];
 
       // check the active tab of each group
--- a/browser/components/tabview/test/browser_tabview_bug595436.js
+++ b/browser/components/tabview/test/browser_tabview_bug595436.js
@@ -31,17 +31,17 @@ function test() {
 
     EventUtils.synthesizeKey("VK_ESCAPE", {}, cw);
     assertSearchIsDisabled();
   }
 
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     cw = win.TabView.getContentWindow();
     search = cw.document.getElementById("search");
     searchButton = cw.document.getElementById("searchbutton");
 
     SimpleTest.waitForFocus(function () {
       assertSearchIsDisabled();
 
--- a/browser/components/tabview/test/browser_tabview_bug595560.js
+++ b/browser/components/tabview/test/browser_tabview_bug595560.js
@@ -4,17 +4,17 @@
 var win;
 var cw;
 
 function test() {
   waitForExplicitFinish();
 
   let onLoad = function (tvwin) {
     win = tvwin;
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
     win.gBrowser.loadOneTab("http://mochi.test:8888/", {inBackground: true});
   };
 
   let onShow = function () {
     cw = win.TabView.getContentWindow();
     ok(win.TabView.isVisible(), "Tab View is visible");
     afterAllTabItemsUpdated(testOne, win);
   };
--- a/browser/components/tabview/test/browser_tabview_bug595804.js
+++ b/browser/components/tabview/test/browser_tabview_bug595804.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var prefsBranch = Cc["@mozilla.org/preferences-service;1"].
                   getService(Ci.nsIPrefService).
                   getBranch("browser.panorama.");
 
-function animateZoom() prefsBranch.getBoolPref("animate_zoom");
+function animateZoom() {
+  return prefsBranch.getBoolPref("animate_zoom");
+}
 
 var contentWindow = null;
 
 registerCleanupFunction(function() {
   // reset to default: true
   prefsBranch.setBoolPref("animate_zoom", true);
 });
 
--- a/browser/components/tabview/test/browser_tabview_bug595930.js
+++ b/browser/components/tabview/test/browser_tabview_bug595930.js
@@ -18,17 +18,17 @@ function onTabViewWindowLoaded() {
 
   // create group which we'll close
   let box1 = new contentWindow.Rect(310, 10, 300, 300);
   let group1 = new contentWindow.GroupItem([], { bounds: box1 });
   ok(group1.isEmpty(), "This group is empty");
   contentWindow.UI.setActive(group1);
   let tab1 = gBrowser.loadOneTab("about:blank#1", {inBackground: true});
   let tab1Item = tab1._tabViewTabItem;
-  ok(group1.getChildren().some(function(child) child == tab1Item), "The tab was made in our new group");
+  ok(group1.getChildren().some(child => child == tab1Item), "The tab was made in our new group");
   is(group1.getChildren().length, 1, "Only one tab in the first group");
 
   group1.addSubscriber("close", function onClose() {
     group1.removeSubscriber("close", onClose);
 
     let onTabViewHidden = function() {
       window.removeEventListener("tabviewhidden", onTabViewHidden, false);
       // assert that we're no longer in tab view
--- a/browser/components/tabview/test/browser_tabview_bug597360.js
+++ b/browser/components/tabview/test/browser_tabview_bug597360.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItems = cw.GroupItems.groupItems;
     let groupItem = groupItems[0];
 
     is(groupItems.length, 1, "There is one group item on startup");
 
     whenTabViewIsHidden(function () {
--- a/browser/components/tabview/test/browser_tabview_bug597980.js
+++ b/browser/components/tabview/test/browser_tabview_bug597980.js
@@ -3,17 +3,17 @@
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(part1);
 }
 
 function part1(win) {
-  registerCleanupFunction(function() win.close());
+  registerCleanupFunction(() => win.close());
 
   let contentWindow = win.TabView.getContentWindow();
   is(contentWindow.GroupItems.groupItems.length, 1, "Has only one group");
 
   let originalTab = win.gBrowser.selectedTab;
   let originalGroup = contentWindow.GroupItems.groupItems[0];
   let newTab = win.gBrowser.loadOneTab("about:blank", {inBackground: true});
   
@@ -56,17 +56,17 @@ function part1(win) {
         win.close();
         newWindowWithTabView(part2);
       });
     }, 490);
   }, 10)
 }
 
 function part2(win) {
-  registerCleanupFunction(function() win.close());
+  registerCleanupFunction(() => win.close());
 
   let newTab = win.gBrowser.loadOneTab("about:blank", {inBackground: true});
   hideTabView(function() {
     let selectedTab = win.gBrowser.selectedTab;
     isnot(selectedTab, newTab, "They are different tabs");
 
     // switch the selected tab to new tab
     win.gBrowser.selectedTab = newTab;
--- a/browser/components/tabview/test/browser_tabview_bug598375.js
+++ b/browser/components/tabview/test/browser_tabview_bug598375.js
@@ -1,22 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItem = cw.GroupItems.groupItems[0];
     groupItem.setBounds(new cw.Rect(cw.innerWidth - 200, 0, 200, 200));
 
-    whenTabViewIsHidden(function () waitForFocus(finish), win);
+    whenTabViewIsHidden(() => waitForFocus(finish), win);
 
     waitForFocus(function () {
       let button = cw.document.getElementById("exit-button");
       EventUtils.synthesizeMouseAtCenter(button, {}, cw);
     }, cw);
   });
 
   // This test relies on the test timing out in order to indicate failure so
--- a/browser/components/tabview/test/browser_tabview_bug602432.js
+++ b/browser/components/tabview/test/browser_tabview_bug602432.js
@@ -11,17 +11,17 @@ function test() {
       ok(earliestUpdateTime <= updateTime, "Stacked group item update (" +
          updateTime + ") > first item (" + earliestUpdateTime + ")");
     });
   }
 
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let numTabsToUpdate = 10;
     let groupItem = createGroupItemWithBlankTabs(win, 150, 150, 100, numTabsToUpdate, false);
     ok(groupItem.isStacked(), "groupItem is stacked");
 
     let cw = win.TabView.getContentWindow();
     cw.TabItems.pausePainting();
 
--- a/browser/components/tabview/test/browser_tabview_bug607108.js
+++ b/browser/components/tabview/test/browser_tabview_bug607108.js
@@ -51,17 +51,17 @@ function test() {
     EventUtils.synthesizeKey("VK_RETURN", {}, cw);
 
     groupItem = cw.GroupItems.groupItems[1];
     is(groupItem.getTitle(), "t", "new groupItem's title is correct");
     closeGroupItem(groupItem, callback);
   };
 
   let onLoad = function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     for (let i = 0; i < 2; i++)
       win.gBrowser.addTab();
   };
 
   let onShow = function (win) {
     cw = win.TabView.getContentWindow();
     assertNumberOfGroupItems(1);
--- a/browser/components/tabview/test/browser_tabview_bug612470.js
+++ b/browser/components/tabview/test/browser_tabview_bug612470.js
@@ -3,17 +3,17 @@
 
 // Tests that groups behave properly when closing all tabs but app tabs.
 
 function test() {
   let cw, win, groupItem;
 
   let onLoad = function (tvwin) {
     win = tvwin;
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
     win.gBrowser.pinTab(win.gBrowser.tabs[0]);
     win.gBrowser.loadOneTab("about:blank", {inBackground: true});
   };
 
   let onShow = function () {
     cw = win.TabView.getContentWindow();
     is(cw.GroupItems.groupItems.length, 1, "There's only one group");
 
--- a/browser/components/tabview/test/browser_tabview_bug616729.js
+++ b/browser/components/tabview/test/browser_tabview_bug616729.js
@@ -47,24 +47,24 @@ function test() {
       let test = tests.shift();
 
       // prepare
       let items = groupItem.getChildren();
       while (test.length < items.length)
         items[items.length-1].close();
 
       let orig = cw.Utils.copy(items);
-      items.sort(function (a, b) test.indexOf(a) - test.indexOf(b));
+      items.sort((a, b) => test.indexOf(a) - test.indexOf(b));
 
       // order and check
       groupItem.reorderTabsBasedOnTabItemOrder();
       assertCorrectItemOrder(items);
 
       // revert to original item order
-      items.sort(function (a, b) orig.indexOf(a) - orig.indexOf(b));
+      items.sort((a, b) => orig.indexOf(a) - orig.indexOf(b));
       groupItem.reorderTabsBasedOnTabItemOrder();
     }
 
     testMoveBetweenGroups();
   }
 
   let testMoveBetweenGroups = function () {
     let groupItem = createGroupItem();
--- a/browser/components/tabview/test/browser_tabview_bug624692.js
+++ b/browser/components/tabview/test/browser_tabview_bug624692.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   requestLongerTimeout(2);
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItem = cw.GroupItems.groupItems[0];
     let tabItem = groupItem.getChild(0);
 
     hideGroupItem(groupItem, function () {
       unhideGroupItem(groupItem, function () {
         let bounds = tabItem.getBounds();
--- a/browser/components/tabview/test/browser_tabview_bug624727_perwindowpb.js
+++ b/browser/components/tabview/test/browser_tabview_bug624727_perwindowpb.js
@@ -106,17 +106,17 @@ function test() {
     win.addEventListener("load", function onLoad() {
       win.removeEventListener("load", onLoad, false);
       executeSoon(function() { aCallback(win) });
     }, false);
   }
 
   waitForExplicitFinish();
   testOnWindow(false, function(publicWindow) {
-    registerCleanupFunction(function () publicWindow.TabView.hide());
+    registerCleanupFunction(() => publicWindow.TabView.hide());
     assertValidPrerequisites(publicWindow, 'start');
 
     showTabView(function () {
       let cw = publicWindow.TabView.getContentWindow();
       assertNumberOfGroups(cw, 'start', 1);
       createGroupItem(publicWindow);
 
       afterAllTabsLoaded(function () {
--- a/browser/components/tabview/test/browser_tabview_bug624953.js
+++ b/browser/components/tabview/test/browser_tabview_bug624953.js
@@ -13,11 +13,11 @@ function test() {
 
     groupItem.setSize(100, 100, true);
     groupItem.setUserSize();
     ok(!groupItem.isStacked(), 'groupItem is still not stacked');
 
     cw.GroupItems.resumeArrange();
     ok(groupItem.isStacked(), 'groupItem is now stacked');
 
-    closeGroupItem(groupItem, function () hideTabView(finish));
+    closeGroupItem(groupItem, () => hideTabView(finish));
   });
 }
--- a/browser/components/tabview/test/browser_tabview_bug625269.js
+++ b/browser/components/tabview/test/browser_tabview_bug625269.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
   newWindowWithTabView(onTabViewShown, null, 850);
 }
 
 function onTabViewShown(win) {
-  registerCleanupFunction(function () win.close());
+  registerCleanupFunction(() => win.close());
 
   let contentWindow = win.TabView.getContentWindow();
   let currentGroup = contentWindow.GroupItems.getActiveGroupItem();
 
   function checkResized(diffX, diffY, shouldResize, text, callback) {
     let {width: origWidth, height: origHeight} = currentGroup.getBounds();
 
     resizeWindow(win, diffX, diffY, function () {
--- a/browser/components/tabview/test/browser_tabview_bug626791.js
+++ b/browser/components/tabview/test/browser_tabview_bug626791.js
@@ -87,17 +87,17 @@ function test() {
 
       next();
     }, win);
   }
 
   let testDragToCreateOrphan = function (tab) {
     if (!tab) {
       let tab = win.gBrowser.loadOneTab('about:blank', {inBackground: true});
-      afterAllTabsLoaded(function () testDragToCreateOrphan(tab), win);
+      afterAllTabsLoaded(() => testDragToCreateOrphan(tab), win);
       return;
     }
 
     prefix = 'drag-to-create-orphan';
     assertNumberOfTabs(2);
     assertToolbarButtonNotExists();
 
     let width = cw.innerWidth;
--- a/browser/components/tabview/test/browser_tabview_bug628061.js
+++ b/browser/components/tabview/test/browser_tabview_bug628061.js
@@ -31,23 +31,23 @@ function test() {
 
 function testOne() {
   newWindowWithTabView(
     function(win) {
       testTwo();
       win.close();
     },
     function(win) {
-      registerCleanupFunction(function() win.close());
+      registerCleanupFunction(() => win.close());
       is(win.document.getElementById("tabviewGroupsNumber").getAttribute("groups"),
          "1", "There is one group");
     });
 }
 
 function testTwo() {
   newWindowWithState(state, function(win) {
-    registerCleanupFunction(function() win.close());
+    registerCleanupFunction(() => win.close());
 
     is(win.document.getElementById("tabviewGroupsNumber").getAttribute("groups"),
        "2", "There are two groups");
     waitForFocus(finish);
   });
 }
--- a/browser/components/tabview/test/browser_tabview_bug628270.js
+++ b/browser/components/tabview/test/browser_tabview_bug628270.js
@@ -88,17 +88,17 @@ function test() {
         gBrowser.removeTab(gBrowser.tabs[1]);
         hideTabView(finishTest);
       });
     });
   }
 
   waitForExplicitFinish();
   assertTabViewIsHidden();
-  registerCleanupFunction(function () TabView.hide());
+  registerCleanupFunction(() => TabView.hide());
 
   showTabView(function () {
     cw = TabView.getContentWindow();
     assertValidPrerequisites();
 
     createGroupItem();
     afterAllTabsLoaded(testRestoreTabFromInactiveGroup);
   });
--- a/browser/components/tabview/test/browser_tabview_bug628887.js
+++ b/browser/components/tabview/test/browser_tabview_bug628887.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function(win) {
-    registerCleanupFunction(function() win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItemOne = cw.GroupItems.groupItems[0];
 
     let groupItemTwo = createGroupItemWithBlankTabs(win, 100, 100, 40, 2);
     ok(groupItemTwo.isStacked(), "groupItem is now stacked");
 
     is(win.gBrowser.tabs.length, 3, "There are three tabs");
--- a/browser/components/tabview/test/browser_tabview_bug629195.js
+++ b/browser/components/tabview/test/browser_tabview_bug629195.js
@@ -14,17 +14,17 @@ function test() {
 
       contentWindow = win.document.getElementById("tab-view").contentWindow;
       is(contentWindow.GroupItems.groupItems.length, 1, "There is one group");
       is(contentWindow.GroupItems.groupItems[0].getChildren().length, 1,
          "The group has only one tab item");
 
       // show the undo close group button
       let group = contentWindow.GroupItems.groupItems[0];
-      hideGroupItem(group, function () restore(group.id));
+      hideGroupItem(group, () => restore(group.id));
     },
     function(newWin) {
       win = newWin;
       originalTab = win.gBrowser.visibleTabs[0];
       win.gBrowser.addTab();
       win.gBrowser.pinTab(originalTab);
     }
   );
--- a/browser/components/tabview/test/browser_tabview_bug630102.js
+++ b/browser/components/tabview/test/browser_tabview_bug630102.js
@@ -26,17 +26,17 @@ function test() {
     is(tabItems[1].tab, win.gBrowser.tabs[1], "The second tab item is linked to the second tab");
     is(tabItems[2].tab, win.gBrowser.tabs[2], "The third tab item is linked to the third tab");
 
     finish();
   };
 
   let onLoad = function (tvwin) {
     win = tvwin;
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     for (let i = 0; i < 2; i++)
       win.gBrowser.loadOneTab("about:blank", {inBackground: true});
 
     [originalTab, newTab1, newTab2] = win.gBrowser.tabs;
     win.gBrowser.pinTab(newTab1);
   };
 
--- a/browser/components/tabview/test/browser_tabview_bug630157.js
+++ b/browser/components/tabview/test/browser_tabview_bug630157.js
@@ -65,15 +65,15 @@ function test() {
     simulateDoubleClick(container, 2);
     assertNumberOfTabs(1);
 
     groupItem.close();
     hideTabView(finishTest);
   }
 
   waitForExplicitFinish();
-  registerCleanupFunction(function () TabView.hide());
+  registerCleanupFunction(() => TabView.hide());
 
   showTabView(function () {
     cw = TabView.getContentWindow();
     testDoubleClick();
   });
 }
--- a/browser/components/tabview/test/browser_tabview_bug631752.js
+++ b/browser/components/tabview/test/browser_tabview_bug631752.js
@@ -47,34 +47,34 @@ function test() {
       groupItem.removeSubscriber("expanded", onExpanded);
       dragTabItem();
     });
 
     groupItem.addSubscriber("collapsed", function onCollapsed() {
       groupItem.removeSubscriber("collapsed", onCollapsed);
 
       let secondGroup = cw.GroupItems.groupItems[1];
-      closeGroupItem(secondGroup, function () hideTabView(finishTest));
+      closeGroupItem(secondGroup, () => hideTabView(finishTest));
     });
 
     groupItem.expand();
   }
 
   let finishTest = function () {
     is(cw.GroupItems.groupItems.length, 1, "there is one groupItem");
     is(gBrowser.tabs.length, 1, "there is one tab");
     ok(!TabView.isVisible(), "tabview is hidden");
 
     finish();
   }
 
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     cw = win.TabView.getContentWindow();
 
     groupItem = cw.GroupItems.groupItems[0];
     groupItem.setSize(200, 200, true);
 
     for (let i = 0; i < 9; i++)
       win.gBrowser.addTab();
--- a/browser/components/tabview/test/browser_tabview_bug633190.js
+++ b/browser/components/tabview/test/browser_tabview_bug633190.js
@@ -7,17 +7,17 @@ var contentWindow;
 function test() {
   waitForExplicitFinish();
 
   test1();
 }
 
 // Open a new tab when the active tab item belongs to a group item.
 function test1() {
-  registerCleanupFunction(function () TabView.hide());
+  registerCleanupFunction(() => TabView.hide());
 
   showTabView(function() {
     ok(origTab._tabViewTabItem.parent, "The original tab belongs to a group");
 
     contentWindow = TabView.getContentWindow();
     contentWindow.UI.setActive(origTab._tabViewTabItem);
 
     testCreateTabAndThen(test2);
@@ -64,17 +64,17 @@ function testCreateTabAndThen(callback) 
 
     // ensure that the default tabview listener is called before so the 
     // tab._tabViewTabItem exists
     executeSoon(function() {
       let tab = event.target;
       tabItem = tab._tabViewTabItem;
       ok(tabItem, "Tab item is available after tab open");
 
-      registerCleanupFunction(function () gBrowser.removeTab(tab))
+      registerCleanupFunction(() => gBrowser.removeTab(tab))
 
       tabItem.addSubscriber("zoomedIn", function onZoomedIn() {
         tabItem.removeSubscriber("zoomedIn", onZoomedIn);
 
         is(gBrowser.selectedTab, tab,
           "The selected tab is the same as the newly opened tab");
         executeSoon(callback);
       });
--- a/browser/components/tabview/test/browser_tabview_bug634085.js
+++ b/browser/components/tabview/test/browser_tabview_bug634085.js
@@ -30,17 +30,17 @@ function test() {
   }
 
   waitForExplicitFinish();
 
   showTabView(function () {
     let groupItem = createGroupItemWithBlankTabs(window, 150, 150, 10, numChildren);
 
     registerCleanupFunction(function () {
-      closeGroupItem(groupItem, function () TabView.hide());
+      closeGroupItem(groupItem, () => TabView.hide());
     });
 
     testTopOfStack(groupItem.getChild(1), function () {
       testTopOfStack(groupItem.getChild(6), function () {
         closeGroupItem(groupItem, function () {
           hideTabView(finishTest);
         });
       });
--- a/browser/components/tabview/test/browser_tabview_bug635696.js
+++ b/browser/components/tabview/test/browser_tabview_bug635696.js
@@ -65,13 +65,13 @@ function test() {
     ok(!TabView.isVisible(), "tabview is hidden");
 
     finish();
   }
 
   waitForExplicitFinish();
 
   showTabView(function () {
-    registerCleanupFunction(function () TabView.hide());
+    registerCleanupFunction(() => TabView.hide());
     cw = TabView.getContentWindow();
     next();
   });
 }
--- a/browser/components/tabview/test/browser_tabview_bug643392.js
+++ b/browser/components/tabview/test/browser_tabview_bug643392.js
@@ -21,17 +21,17 @@ var state = {
     }
   }]
 };
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithState(state, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     is(win.gBrowser.tabs.length, 2, "two tabs");
 
     let opts = {animate: true, byMouse: true};
     win.gBrowser.removeTab(win.gBrowser.visibleTabs[0], opts);
 
     let checkTabCount = function () {
       if (win.gBrowser.tabs.length > 1) {
--- a/browser/components/tabview/test/browser_tabview_bug644097.js
+++ b/browser/components/tabview/test/browser_tabview_bug644097.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItem = cw.GroupItems.groupItems[0];
 
     // create some tabs with favIcons
     for (let i = 0; i < 3; i++)
       win.gBrowser.loadOneTab("http://mochi.test:8888/browser/browser/components/tabview/test/test_bug644097.html", {inBackground: true});
 
--- a/browser/components/tabview/test/browser_tabview_bug648882.js
+++ b/browser/components/tabview/test/browser_tabview_bug648882.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let tab = win.gBrowser.tabs[0];
     let tabItem = tab._tabViewTabItem;
     let isIdle = false;
 
     // We replace UI.isIdle() here to not rely on setTimeout(). While this
     // function returns false (busy) we expect no tabItem updates to happen.
--- a/browser/components/tabview/test/browser_tabview_bug649319.js
+++ b/browser/components/tabview/test/browser_tabview_bug649319.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
-    waitForFocus(function () testScenarios(win));
+    registerCleanupFunction(() => win.close());
+    waitForFocus(() => testScenarios(win));
   });
 }
 
 function testScenarios(win) {
   let simulateDragDrop = function (target) {
     EventUtils.synthesizeMouseAtCenter(target, {type: "mousedown"}, cw);
     EventUtils.synthesizeMouse(target, 40, 20, {type: "mousemove"}, cw);
     EventUtils.synthesizeMouse(target, 80, 20, {type: "mouseup"}, cw);
--- a/browser/components/tabview/test/browser_tabview_bug651311.js
+++ b/browser/components/tabview/test/browser_tabview_bug651311.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   let callCount = 0;
 
   newWindow(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     win.TabView._initFrame(function () {
       is(callCount++, 0, "call count is zero");
       ok(win.TabView.getContentWindow().UI, "content window is loaded");
     });
 
     win.TabView._initFrame(function () {
       is(callCount++, 1, "call count is one");
--- a/browser/components/tabview/test/browser_tabview_bug654295.js
+++ b/browser/components/tabview/test/browser_tabview_bug654295.js
@@ -41,17 +41,17 @@ function test() {
           '"2":{"bounds":{"left":309,"top":5,"width":267,"height":226},' +
           '"userSize":null,"title":"","id":2}}',
         "tabview-ui": '{"pageBounds":{"left":0,"top":0,"width":788,"height":548}}'
       }, sizemode:"normal"
     }]
   };
 
   newWindowWithState(newState, function(win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     whenTabViewIsShown(function() {
       let contentWindow = win.TabView.getContentWindow();
 
       is(contentWindow.GroupItems.groupItems.length, 2, "There are still two groups");
       is(win.gBrowser.tabs.length, 1, "There is only one tab");
 
       finish();
--- a/browser/components/tabview/test/browser_tabview_bug654721.js
+++ b/browser/components/tabview/test/browser_tabview_bug654721.js
@@ -22,17 +22,17 @@ var state = {
     }
   }]
 };
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithState(state, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     showTabView(function () {
       let cw = win.TabView.getContentWindow();
       let groupItems = cw.GroupItems.groupItems;
       is(groupItems.length, 2, "two groupItems");
 
       let [group1, group2] = groupItems;
 
--- a/browser/components/tabview/test/browser_tabview_bug654941.js
+++ b/browser/components/tabview/test/browser_tabview_bug654941.js
@@ -6,17 +6,17 @@ function test() {
 
   let compareCanvasSize = function (prefix, width, height, tabItem) {
     let canvas = tabItem.$canvas[0];
     is(canvas.width, width, prefix + ": canvas widths are equal (" + width + "px)");
     is(canvas.height, height, prefix + ": canvas heights are equal (" + height + "px)");
   };
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItem = cw.GroupItems.groupItems[0];
     let tabItem = groupItem.getChild(0);
 
     afterAllTabItemsUpdated(function () {
       let {width, height} = tabItem.$canvas[0];
 
--- a/browser/components/tabview/test/browser_tabview_bug656913.js
+++ b/browser/components/tabview/test/browser_tabview_bug656913.js
@@ -15,17 +15,17 @@ function test() {
   });
 
   afterAllTabsLoaded(function() {
     showTabView(function() {
       hideTabView(function() {
         newTab.linkedBrowser.loadURI(urlBase + "dummy_page.html");
 
         newWindowWithTabView(function(win) {
-          registerCleanupFunction(function() win.close());
+          registerCleanupFunction(() => win.close());
 
           let contentWindow = win.TabView.getContentWindow();
 
           EventUtils.synthesizeKey("d", { }, contentWindow);
           EventUtils.synthesizeKey("u", { }, contentWindow);
           EventUtils.synthesizeKey("m", { }, contentWindow);
 
           let resultsElement = contentWindow.document.getElementById("results");
--- a/browser/components/tabview/test/browser_tabview_bug662266.js
+++ b/browser/components/tabview/test/browser_tabview_bug662266.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     for (let i = 0; i < 4; i++)
       win.gBrowser.loadOneTab("about:blank", {inBackground: true});
 
     let cw = win.TabView.getContentWindow();
     let groupItem = cw.GroupItems.groupItems[0];
     let children = groupItem.getChildren();
 
--- a/browser/components/tabview/test/browser_tabview_bug663421.js
+++ b/browser/components/tabview/test/browser_tabview_bug663421.js
@@ -71,17 +71,17 @@ function test() {
     }, win);
   }
 
   let tests = [test1, test2, test3, test4];
 
   waitForExplicitFinish();
 
   newWindowWithTabView(function (aWin) {
-    registerCleanupFunction(function () aWin.close());
+    registerCleanupFunction(() => aWin.close());
 
     win = aWin;
     cw = win.TabView.getContentWindow();
     groupItem = createEmptyGroupItem(cw, 200, 200, 20);
 
     checkNumberOfGroupItems(2);
     next();
   });
--- a/browser/components/tabview/test/browser_tabview_bug665502.js
+++ b/browser/components/tabview/test/browser_tabview_bug665502.js
@@ -5,17 +5,17 @@ function test() {
   waitForExplicitFinish();
 
   let onLoad = function (win) {
     for (let i = 0; i < 2; i++)
       win.gBrowser.addTab();
   };
 
   let onShow = function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let cw = win.TabView.getContentWindow();
     let groupItem = cw.GroupItems.groupItems[0];
 
     groupItem.setSize(400, 200, true);
 
     let tabItem = groupItem.getChild(0);
     let bounds = tabItem.getBounds();
--- a/browser/components/tabview/test/browser_tabview_bug669694.js
+++ b/browser/components/tabview/test/browser_tabview_bug669694.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   function onLoad(win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let tab = win.gBrowser.addTab();
     win.gBrowser.pinTab(tab);
   }
 
   function onShow(win) {
     let tabs = win.gBrowser.tabs;
 
--- a/browser/components/tabview/test/browser_tabview_bug673196.js
+++ b/browser/components/tabview/test/browser_tabview_bug673196.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   function onLoad(win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
     win.gBrowser.addTab();
   }
 
   function onShow(win) {
     let cw = win.TabView.getContentWindow();
     let group = cw.GroupItems.groupItems[0];
 
     // shrink the group to make some room for dragging
--- a/browser/components/tabview/test/browser_tabview_bug685476.js
+++ b/browser/components/tabview/test/browser_tabview_bug685476.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
 
   showTabView(function () {
     let tab = gBrowser.addTab();
-    registerCleanupFunction(function () gBrowser.removeTab(tab));
+    registerCleanupFunction(() => gBrowser.removeTab(tab));
 
     let cw = TabView.getContentWindow();
     whenAppTabIconAdded(cw.GroupItems.groupItems[0], function() {
       let body = cw.document.body;
       let appTabIcon = cw.iQ(".appTabTray .appTabIcon")[0];
 
       EventUtils.synthesizeMouseAtCenter(appTabIcon, {type: "mousedown"}, cw);
       EventUtils.synthesizeMouse(body, 500, 100, {type: "mousemove"}, cw);
--- a/browser/components/tabview/test/browser_tabview_bug697390.js
+++ b/browser/components/tabview/test/browser_tabview_bug697390.js
@@ -21,17 +21,17 @@ var state = {
     }
   }]
 };
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithState(state, function(win) {
-    registerCleanupFunction(function() win.close());
+    registerCleanupFunction(() => win.close());
 
     win.gBrowser.addTab();
 
     ok(win.gBrowser.tabs[0].hidden, "The first tab is hidden");
     win.gBrowser.selectedTab = win.gBrowser.tabs[0];
 
     function onTabViewFrameInitialized() {
       win.removeEventListener(
--- a/browser/components/tabview/test/browser_tabview_bug706430.js
+++ b/browser/components/tabview/test/browser_tabview_bug706430.js
@@ -34,17 +34,17 @@ var state2 = {
 
 var ss = Cc["@mozilla.org/browser/sessionstore;1"]
          .getService(Ci.nsISessionStore);
 
 function test() {
   waitForExplicitFinish();
 
   newWindowWithState(state1, function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     showTabView(function () {
       let cw = win.TabView.getContentWindow();
       let [group1, group2] = cw.GroupItems.groupItems;
       let [tab1, tab2] = win.gBrowser.tabs;
 
       checkUrl(group1.getChild(0), "about:robots#1", "tab1 is in first group");
       checkUrl(group2.getChild(0), "about:robots#2", "tab2 is in second group");
--- a/browser/components/tabview/test/browser_tabview_bug707466.js
+++ b/browser/components/tabview/test/browser_tabview_bug707466.js
@@ -38,17 +38,17 @@ function test() {
           '"2":{"bounds":{"left":309,"top":5,"width":267,"height":226},' +
           '"userSize":null,"title":"","id":2}}',
         "tabview-ui": '{"pageBounds":{"left":0,"top":0,"width":788,"height":548}}'
       }, sizemode:"normal"
     }]
   };
 
   newWindowWithState(newState, function(win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     whenTabViewIsShown(function() {
       let cw = win.TabView.getContentWindow();
 
       is(cw.GroupItems.groupItems.length, 2, "There are still two groups");
       is(win.gBrowser.tabs.length, 1, "There is only one tab");
       is(cw.UI.getActiveTab(), win.gBrowser.selectedTab._tabViewTabItem, "The last tab is selected");
 
--- a/browser/components/tabview/test/browser_tabview_bug712203.js
+++ b/browser/components/tabview/test/browser_tabview_bug712203.js
@@ -39,17 +39,17 @@ function test() {
           '"2":{"bounds":{"left":309,"top":5,"width":267,"height":226},' +
           '"userSize":null,"title":"","id":2}}',
         "tabview-ui": '{"pageBounds":{"left":0,"top":0,"width":788,"height":548}}'
       }, sizemode:"normal"
     }]
   };
 
   newWindowWithState(newState, function(win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     let selectedTab = win.gBrowser.selectedTab;
 
     win.addEventListener("tabviewframeinitialized", function onInit() {
       win.removeEventListener("tabviewframeinitialized", onInit, false);
       // ensure the TabView.contentWindow is set.
       executeSoon(function() {
         let cw = win.TabView.getContentWindow();
--- a/browser/components/tabview/test/browser_tabview_dragdrop.js
+++ b/browser/components/tabview/test/browser_tabview_dragdrop.js
@@ -54,17 +54,17 @@ function addTest(contentWindow, groupOne
     groupTwo.removeSubscriber("childAdded", endGame);
 
     is(groupOne.getChildren().length, --groupOneTabItemCount,
        "The number of children in group one is decreased by 1");
     is(groupTwo.getChildren().length, ++groupTwoTabItemCount,
        "The number of children in group two is increased by 1");
 
     closeGroupItem(groupOne, function () {
-      closeGroupItem(groupTwo, function () hideTabView(finish));
+      closeGroupItem(groupTwo, () => hideTabView(finish));
     });
   }
 
   groupTwo.addSubscriber("childAdded", endGame);
   simulateDragDrop(tabItem.container, offsetX, offsetY, contentWindow);
 }
 
 function simulateDragDrop(element, offsetX, offsetY, contentWindow) {
--- a/browser/components/tabview/test/browser_tabview_expander.js
+++ b/browser/components/tabview/test/browser_tabview_expander.js
@@ -191,31 +191,31 @@ function onTabViewWindowLoaded(win) {
       let overlay = contentWindow.document.getElementById("expandedTray");
       ok(overlay, "The expanded tray exists.");
       
       let activeTab = contentWindow.UI.getActiveTab();
       ok(activeTab, "There is an active tab.");
       let originalTabItem = originalTab._tabViewTabItem;
 
       isnot(activeTab, originalTabItem, "But it's not what it was a moment ago.");
-      let someChildIsActive = group.getChildren().some(function(child)
+      let someChildIsActive = group.getChildren().some(child =>
                               child == activeTab);
       ok(someChildIsActive, "Now one of the children in the group is active.");
             
       // now activate Panorama...
       win.addEventListener("tabviewhidden", stage4hidden, false);
       win.TabView.toggle();
     };
   
     let stage4hidden = function() {
       win.removeEventListener("tabviewhidden", stage4hidden, false);
       
       isnot(win.gBrowser.selectedTab, originalTab, "We did not enter the original tab.");
 
-      let someChildIsSelected = group.getChildren().some(function(child)
+      let someChildIsSelected = group.getChildren().some(child =>
                                   child.tab == win.gBrowser.selectedTab);
       ok(someChildIsSelected, "Instead we're in one of the stack's children.");
       
       win.addEventListener("tabviewshown", stage4shown, false);
       win.TabView.toggle();
     };
     
     let stage4shown = function() {
--- a/browser/components/tabview/test/browser_tabview_launch.js
+++ b/browser/components/tabview/test/browser_tabview_launch.js
@@ -3,20 +3,20 @@
 
 var newWin;
 
 // ----------
 function test() {
   waitForExplicitFinish();
 
   let panelSelected = false;
-  registerCleanupFunction(function () ok(panelSelected, "panel is selected"));
+  registerCleanupFunction(() => ok(panelSelected, "panel is selected"));
 
   let onLoad = function (win) {
-    registerCleanupFunction(function () win.close());
+    registerCleanupFunction(() => win.close());
 
     newWin = win;
 
     let onSelect = function(event) {
       if (deck != event.target)
         return;
 
       let iframe = win.document.getElementById("tab-view");
--- a/browser/components/tabview/test/browser_tabview_startup_transitions.js
+++ b/browser/components/tabview/test/browser_tabview_startup_transitions.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var prefsBranch = Cc["@mozilla.org/preferences-service;1"].
                   getService(Ci.nsIPrefService).
                   getBranch("browser.panorama.");
 
-function animateZoom() prefsBranch.getBoolPref("animate_zoom");
+function animateZoom() {
+  return prefsBranch.getBoolPref("animate_zoom");
+}
 
 function test() {
   waitForExplicitFinish();
 
   let win = window.openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no",
                               "about:blank", null, null, null, true);
 
   registerCleanupFunction(function() {
--- a/browser/components/tabview/test/head.js
+++ b/browser/components/tabview/test/head.js
@@ -108,17 +108,17 @@ function newWindowWithTabView(shownCallb
                               ",width=" + winWidth, "about:blank");
 
   whenWindowLoaded(win, function () {
     if (loadCallback)
       loadCallback(win);
   });
 
   whenDelayedStartupFinished(win, function () {
-    showTabView(function () shownCallback(win), win);
+    showTabView(() => shownCallback(win), win);
   });
 }
 
 // ----------
 function afterAllTabsLoaded(callback, win) {
   const TAB_STATE_NEEDS_RESTORE = 1;
 
   win = win || window;
@@ -360,17 +360,17 @@ function newWindowWithState(state, callb
 // ----------
 function restoreTab(callback, index, win) {
   win = win || window;
 
   let tab = win.undoCloseTab(index || 0);
   let tabItem = tab._tabViewTabItem;
 
   let finalize = function () {
-    afterAllTabsLoaded(function () callback(tab), win);
+    afterAllTabsLoaded(() => callback(tab), win);
   };
 
   if (tabItem._reconnected) {
     finalize();
     return;
   }
 
   tab._tabViewTabItem.addSubscriber("reconnected", function onReconnected() {
--- a/browser/components/tabview/trench.js
+++ b/browser/components/tabview/trench.js
@@ -82,17 +82,19 @@ Trench.prototype = {
     return "[Trench " + this.edge + " " + this.type +
            (this.parentItem ? " (" + this.parentItem + ")" : "") +
            "]";
   },
 
   //----------
   // Variable: radius
   // (integer) radius is how far away we should snap from
-  get radius() this.customRadius || Trenches.defaultRadius,
+  get radius() {
+    return this.customRadius || Trenches.defaultRadius;
+  },
 
   setParentItem: function Trench_setParentItem(item) {
     if (!item.isAnItem) {
       Utils.assert(false, "parentItem must be an Item");
       return false;
     }
     this.parentItem = item;
     return true;
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -124,17 +124,19 @@ this.Translation = {
  * - originalShown, boolean indicating if the original or translated
  *   version of the page is shown.
  */
 function TranslationUI(aBrowser) {
   this.browser = aBrowser;
 }
 
 TranslationUI.prototype = {
-  get browser() this._browser,
+  get browser() {
+    return this._browser;
+  },
   set browser(aBrowser) {
     if (this._browser)
       this._browser.messageManager.removeMessageListener("Translation:Finished", this);
     aBrowser.messageManager.addMessageListener("Translation:Finished", this);
     this._browser = aBrowser;
   },
   translate: function(aFrom, aTo) {
     if (aFrom == aTo ||
@@ -196,17 +198,19 @@ TranslationUI.prototype = {
 
     let addId = this.originalShown ? "translate" : "translated";
     PopupNotifications.show(this.browser, addId, null,
                             addId + "-notification-icon", null, null,
                             {dismissed: true, eventCallback: callback});
   },
 
   _state: 0,
-  get state() this._state,
+  get state() {
+    return this._state;
+  },
   set state(val) {
     let notif = this.notificationBox.getNotificationWithValue("translation");
     if (notif)
       notif.state = val;
     this._state = val;
   },
 
   originalShown: true,
@@ -218,17 +222,19 @@ TranslationUI.prototype = {
   },
 
   showTranslatedContent: function() {
     this.originalShown = false;
     this.showURLBarIcon();
     this.browser.messageManager.sendAsyncMessage("Translation:ShowTranslation");
   },
 
-  get notificationBox() this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser),
+  get notificationBox() {
+    return this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser);
+  },
 
   showTranslationInfoBar: function() {
     let notificationBox = this.notificationBox;
     let notif = notificationBox.appendNotification("", "translation", null,
                                                    notificationBox.PRIORITY_INFO_HIGH);
     notif.init(this);
     return notif;
   },
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -125,16 +125,22 @@
 }
 
 /* LINKS */
 .newtab-link {
   border-radius: 10px;
   overflow: hidden;
 }
 
+/***
+ * If you change the sizes here, change them in newTab.css
+ * and the preference values:
+ * toolkit.pageThumbs.minWidth
+ * toolkit.pageThumbs.minHeight
+ */
 /* THUMBNAILS */
 .newtab-thumbnail {
   background-origin: padding-box;
   background-clip: padding-box;
   background-repeat: no-repeat;
   background-size: cover;
   border-radius: 8px 8px 0px 0px;
   height: 180px;
--- a/config/config.mk
+++ b/config/config.mk
@@ -619,35 +619,62 @@ EXPAND_CC = $(EXPAND_LIBS_EXEC) --uselis
 EXPAND_CCC = $(EXPAND_LIBS_EXEC) --uselist -- $(CCC)
 EXPAND_LD = $(EXPAND_LIBS_EXEC) --uselist -- $(LD)
 EXPAND_MKSHLIB_ARGS = --uselist
 ifdef SYMBOL_ORDER
 EXPAND_MKSHLIB_ARGS += --symbol-order $(SYMBOL_ORDER)
 endif
 EXPAND_MKSHLIB = $(EXPAND_LIBS_EXEC) $(EXPAND_MKSHLIB_ARGS) -- $(MKSHLIB)
 
+# $(call CHECK_SYMBOLS,lib,PREFIX,dep_name,test)
+# Checks that the given `lib` doesn't contain dependency on symbols with a
+# version starting with `PREFIX`_ and matching the `test`. `dep_name` is only
+# used for the error message.
+# `test` is an awk expression using the information in the variable `v` which
+# contains a list of version items ([major, minor, ...]).
+define CHECK_SYMBOLS
+@$(TOOLCHAIN_PREFIX)readelf -sW $(1) | \
+awk '$$8 ~ /@$(2)_/ { \
+	split($$8,a,"@"); \
+	split(a[2],b,"_"); \
+	split(b[2],v,"."); \
+	if ($(4)) { \
+		if (!found) { \
+			print "TEST-UNEXPECTED-FAIL | check_stdcxx | We do not want these $(3) symbols to be used:" \
+		} \
+		print " ",$$8; \
+		found=1 \
+	} \
+} \
+END { \
+	if (found) { \
+		exit(1) \
+	} \
+}'
+endef
+
 ifneq (,$(MOZ_LIBSTDCXX_TARGET_VERSION)$(MOZ_LIBSTDCXX_HOST_VERSION))
-ifneq ($(OS_ARCH),Darwin)
-CHECK_STDCXX = @$(TOOLCHAIN_PREFIX)objdump -p $(1) | grep -e 'GLIBCXX_3\.4\.\(1[1-9]\|[2-9][0-9]\)' > /dev/null && echo 'TEST-UNEXPECTED-FAIL | check_stdcxx | We do not want these libstdc++ symbols to be used:' && $(TOOLCHAIN_PREFIX)objdump -T $(1) | grep -e 'GLIBCXX_3\.4\.\(1[1-9]\|[2-9][0-9]\)' && exit 1 || true
-endif
+CHECK_STDCXX = $(call CHECK_SYMBOLS,$(1),GLIBCXX,libstdc++,v[1] > 3 || (v[1] == 3 && v[2] == 4 && v[3] > 10))
+CHECK_GLIBC = $(call CHECK_SYMBOLS,$(1),GLIBC,libc,v[1] > 2 || (v[1] == 2 && v[2] > 7))
 endif
 
 ifeq (,$(filter $(OS_TARGET),WINNT Darwin))
 CHECK_TEXTREL = @$(TOOLCHAIN_PREFIX)readelf -d $(1) | grep TEXTREL > /dev/null && echo 'TEST-UNEXPECTED-FAIL | check_textrel | We do not want text relocations in libraries and programs' || true
 endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),android)
 # While this is very unlikely (libc being added by the compiler at the end
 # of the linker command line), if libmozglue.so ends up after libc.so, all
 # hell breaks loose, so better safe than sorry, and check it's actually the
 # case.
 CHECK_MOZGLUE_ORDER = @$(TOOLCHAIN_PREFIX)readelf -d $(1) | grep NEEDED | awk '{ libs[$$NF] = ++n } END { if (libs["[libmozglue.so]"] && libs["[libc.so]"] < libs["[libmozglue.so]"]) { print "libmozglue.so must be linked before libc.so"; exit 1 } }'
 endif
 
 define CHECK_BINARY
+$(call CHECK_GLIBC,$(1))
 $(call CHECK_STDCXX,$(1))
 $(call CHECK_TEXTREL,$(1))
 $(call LOCAL_CHECKS,$(1))
 $(call CHECK_MOZGLUE_ORDER,$(1))
 endef
 
 # autoconf.mk sets OBJ_SUFFIX to an error to avoid use before including
 # this file
--- a/config/tests/test_mozbuild_reading.py
+++ b/config/tests/test_mozbuild_reading.py
@@ -1,20 +1,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import unicode_literals
 
 import os
+import sys
 import unittest
 
 from mozunit import main
 
 from mozbuild.base import MozbuildObject
+from mozpack.files import FileFinder
+from mozbuild.frontend.context import Files
 from mozbuild.frontend.reader import (
     BuildReader,
     EmptyConfig,
 )
 
 
 class TestMozbuildReading(unittest.TestCase):
     # This hack is needed to appease running in automation.
@@ -61,10 +64,50 @@ class TestMozbuildReading(unittest.TestC
         config = EmptyConfig(root)
         reader = BuildReader(config)
         all_paths = self._mozbuilds(reader)
         paths, contexts = reader.read_relevant_mozbuilds(all_paths)
         self.assertEqual(set(paths.keys()), all_paths)
         self.assertGreaterEqual(len(contexts), len(paths))
 
 
+    def test_orphan_file_patterns(self):
+        if sys.platform == 'win32':
+            raise unittest.SkipTest('failing on windows builds')
+
+        mb = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
+
+        try:
+            config = mb.config_environment
+        except Exception as e:
+            if e.message == 'config.status not available. Run configure.':
+                raise unittest.SkipTest('failing without config.status')
+            raise
+
+        reader = BuildReader(config)
+        all_paths = self._mozbuilds(reader)
+        _, contexts = reader.read_relevant_mozbuilds(all_paths)
+
+        finder = FileFinder(config.topsrcdir, find_executables=False,
+                            ignore=['obj*'])
+
+        def pattern_exists(pat):
+            return [p for p in finder.find(pat)] != []
+
+        for ctx in contexts:
+            if not isinstance(ctx, Files):
+                continue
+            relsrcdir = ctx.relsrcdir
+            if not pattern_exists(os.path.join(relsrcdir, ctx.pattern)):
+                self.fail("The pattern '%s' in a Files() entry in "
+                          "'%s' corresponds to no files in the tree.\n"
+                          "Please update this entry." %
+                          (ctx.pattern, ctx.main_path))
+            test_files = ctx['IMPACTED_TESTS'].files
+            for p in test_files:
+                if not pattern_exists(os.path.relpath(p.full_path, config.topsrcdir)):
+                    self.fail("The pattern '%s' in a dependent tests entry "
+                              "in '%s' corresponds to no files in the tree.\n"
+                              "Please update this entry." %
+                              (p, ctx.main_path))
+
 if __name__ == '__main__':
     main()
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -68,17 +68,17 @@ Animation::SetEffect(KeyframeEffectReadO
 void
 Animation::SetTimeline(AnimationTimeline* aTimeline)
 {
   if (mTimeline == aTimeline) {
     return;
   }
 
   if (mTimeline) {
-    mTimeline->RemoveAnimation(*this);
+    mTimeline->NotifyAnimationUpdated(*this);
   }
 
   mTimeline = aTimeline;
 
   // FIXME(spec): Once we implement the seeking defined in the spec
   // surely this should be SeekFlag::DidSeek but the spec says otherwise.
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
@@ -612,21 +612,19 @@ Animation::ComposeStyle(nsRefPtr<AnimVal
   //     that case the compositor might have been ahead of the main thread so we
   //     should use the current wallclock time to ensure the animation doesn't
   //     temporarily jump backwards.
   //
   // To address each of these cases we temporarily tweak the hold time
   // immediately before updating the style rule and then restore it immediately
   // afterwards. This is purely to prevent visual flicker. Other behavior
   // such as dispatching events continues to rely on the regular timeline time.
+  bool updatedHoldTime = false;
   {
     AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
-    bool updatedHoldTime = false;
-
-    AnimationPlayState playState = PlayState();
 
     if (playState == AnimationPlayState::Pending &&
         mHoldTime.IsNull() &&
         !mStartTime.IsNull()) {
       Nullable<TimeDuration> timeToUse = mPendingReadyTime;
       if (timeToUse.IsNull() &&
           mTimeline &&
           mTimeline->TracksWallclockTime()) {
@@ -637,23 +635,26 @@ Animation::ComposeStyle(nsRefPtr<AnimVal
                             .MultDouble(mPlaybackRate));
         // Push the change down to the effect
         UpdateEffect();
         updatedHoldTime = true;
       }
     }
 
     mEffect->ComposeStyle(aStyleRule, aSetProperties);
+  }
 
-    if (updatedHoldTime) {
-      UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
-    }
+  // Now that the hold time has been restored, update the effect
+  if (updatedHoldTime) {
+    UpdateEffect();
+  }
 
-    mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
-  }
+  MOZ_ASSERT(playState == PlayState(),
+             "Play state should not change during the course of compositing");
+  mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
 }
 
 void
 Animation::NotifyEffectTimingUpdated()
 {
   MOZ_ASSERT(mEffect,
              "We should only update timing effect when we have a target "
              "effect");
@@ -837,49 +838,18 @@ Animation::PauseAt(const TimeDuration& a
 void
 Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
 {
   // We call UpdateFinishedState before UpdateEffect because the former
   // can change the current time, which is used by the latter.
   UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
   UpdateEffect();
 
-  // Unconditionally Add/Remove from the timeline. This is ok because if the
-  // animation has already been added/removed (which will be true more often
-  // than not) the work done by AnimationTimeline/DocumentTimeline is still
-  // negligible and its easier than trying to detect whenever we are switching
-  // to/from being relevant.
-  //
-  // We need to do this after calling UpdateEffect since it updates some
-  // cached state used by IsRelevant.
-  //
-  // Note that we only store relevant animations on the timeline since they
-  // are the only ones that need ticks and are the only ones returned from
-  // AnimationTimeline::GetAnimations. Storing any more than that would mean
-  // that we fail to garbage collect irrelevant animations since the timeline
-  // keeps a strong reference to each animation.
-  //
-  // Once we tick animations from the their timeline, and once we expect
-  // timelines to go in and out of being inactive, we will also need to store
-  // non-idle animations that are waiting for their timeline to become active
-  // on their timeline (as otherwise once the timeline becomes active it will
-  // have no way of notifying its animations). For now, however, we can
-  // simply store just the relevant animations.
   if (mTimeline) {
-    // FIXME: Once we expect animations to go back and forth betweeen being
-    // inactive and active, we will need to store more than just relevant
-    // animations on the timeline. This is because an animation might be
-    // deemed irrelevant because its timeline is inactive. If it is removed
-    // from the timeline at that point the timeline will have no way of
-    // getting the animation to add itself again once it becomes active.
-    if (IsRelevant()) {
-      mTimeline->AddAnimation(*this);
-    } else {
-      mTimeline->RemoveAnimation(*this);
-    }
+    mTimeline->NotifyAnimationUpdated(*this);
   }
 }
 
 void
 Animation::UpdateFinishedState(SeekFlag aSeekFlag,
                                SyncNotifyFlag aSyncNotifyFlag)
 {
   Nullable<TimeDuration> currentTime = GetCurrentTime();
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -135,16 +135,22 @@ public:
    */
   void PauseFromJS(ErrorResult& aRv) { Pause(aRv); }
 
   // Wrapper functions for Animation DOM methods when called from style.
 
   virtual void CancelFromStyle() { DoCancel(); }
 
   virtual void Tick();
+  bool NeedsTicks() const
+  {
+    AnimationPlayState playState = PlayState();
+    return playState == AnimationPlayState::Running ||
+           playState == AnimationPlayState::Pending;
+  }
 
   /**
    * Set the time to use for starting or pausing a pending animation.
    *
    * Typically, when an animation is played, it does not start immediately but
    * is added to a table of pending animations on the document of its effect.
    * In the meantime it sets its hold time to the time from which playback
    * should begin.
--- a/dom/animation/AnimationTimeline.cpp
+++ b/dom/animation/AnimationTimeline.cpp
@@ -5,17 +5,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AnimationTimeline.h"
 #include "mozilla/AnimationComparator.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationTimeline, mWindow, mAnimations)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationTimeline, mWindow,
+                                      mAnimationOrder)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationTimeline)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationTimeline)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationTimeline)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -26,23 +27,26 @@ AnimationTimeline::GetAnimations(Animati
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mWindow);
   if (mWindow) {
     nsIDocument* doc = window->GetDoc();
     if (doc) {
       doc->FlushPendingNotifications(Flush_Style);
     }
   }
 
-  for (auto iter = mAnimations.Iter(); !iter.Done(); iter.Next()) {
-    Animation* animation = iter.Get()->GetKey();
+  aAnimations.SetCapacity(mAnimationOrder.Length());
+
+  for (Animation* animation : mAnimationOrder) {
 
-    MOZ_ASSERT(animation->IsRelevant(),
-               "Animations registered with a timeline should be relevant");
-    MOZ_ASSERT(animation->GetTimeline() == this,
-               "Animation should refer to this timeline");
+    // Skip animations which are no longer relevant or which have been
+    // associated with another timeline. These animations will be removed
+    // on the next tick.
+    if (!animation->IsRelevant() || animation->GetTimeline() != this) {
+      continue;
+    }
 
     // Bug 1174575: Until we implement a suitable PseudoElement interface we
     // don't have anything to return for the |target| attribute of
     // KeyframeEffect(ReadOnly) objects that refer to pseudo-elements.
     // Rather than return some half-baked version of these objects (e.g.
     // we a null effect attribute) we simply don't provide access to animations
     // whose effect refers to a pseudo-element until we can support them
     // properly.
@@ -54,21 +58,20 @@ AnimationTimeline::GetAnimations(Animati
     }
   }
 
   // Sort animations by priority
   aAnimations.Sort(AnimationPtrComparator<nsRefPtr<Animation>>());
 }
 
 void
-AnimationTimeline::AddAnimation(Animation& aAnimation)
+AnimationTimeline::NotifyAnimationUpdated(Animation& aAnimation)
 {
-  mAnimations.PutEntry(&aAnimation);
-}
+  if (mAnimations.Contains(&aAnimation)) {
+    return;
+  }
 
-void
-AnimationTimeline::RemoveAnimation(Animation& aAnimation)
-{
-  mAnimations.RemoveEntry(&aAnimation);
+  mAnimations.PutEntry(&aAnimation);
+  mAnimationOrder.AppendElement(&aAnimation);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/AnimationTimeline.h
+++ b/dom/animation/AnimationTimeline.h
@@ -12,16 +12,22 @@
 #include "nsCycleCollectionParticipant.h"
 #include "js/TypeDecls.h"
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/Attributes.h"
 #include "nsHashKeys.h"
 #include "nsIGlobalObject.h"
 #include "nsTHashtable.h"
 
+// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
+// GetTickCount().
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
 namespace mozilla {
 namespace dom {
 
 class Animation;
 
 class AnimationTimeline
   : public nsISupports
   , public nsWrapperCache
@@ -72,23 +78,37 @@ public:
    * timestamp from TimeStamp::Now() to this method will not return a
    * meaningful result.
    */
   virtual Nullable<TimeDuration> ToTimelineTime(const TimeStamp&
                                                   aTimeStamp) const = 0;
 
   virtual TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const = 0;
 
-  void AddAnimation(Animation& aAnimation);
-  void RemoveAnimation(Animation& aAnimation);
+  /**
+   * Inform this timeline that |aAnimation| which is or was observing the
+   * timeline, has been updated. This serves as both the means to associate
+   * AND disassociate animations with a timeline. The timeline itself will
+   * determine if it needs to begin, continue or stop tracking this animation.
+   */
+  virtual void NotifyAnimationUpdated(Animation& aAnimation);
 
 protected:
   nsCOMPtr<nsIGlobalObject> mWindow;
 
   // Animations observing this timeline
-  typedef nsTHashtable<nsRefPtrHashKey<dom::Animation>> AnimationSet;
-  AnimationSet mAnimations;
+  //
+  // We store them in (a) a hashset for quick lookup, and (b) an array
+  // to maintain a fixed sampling order.
+  //
+  // The array keeps a strong reference to each animation in order
+  // to save some addref/release traffic and because we never dereference
+  // the pointers in the hashset.
+  typedef nsTHashtable<nsPtrHashKey<dom::Animation>> AnimationSet;
+  typedef nsTArray<nsRefPtr<dom::Animation>>         AnimationArray;
+  AnimationSet   mAnimations;
+  AnimationArray mAnimationOrder;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AnimationTimeline_h
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -3,20 +3,21 @@
 /* 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 "DocumentTimeline.h"
 #include "mozilla/dom/DocumentTimelineBinding.h"
 #include "AnimationUtils.h"
 #include "nsContentUtils.h"
+#include "nsDOMMutationObserver.h"
+#include "nsDOMNavigationTiming.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsRefreshDriver.h"
-#include "nsDOMNavigationTiming.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DocumentTimeline, AnimationTimeline,
                                    mDocument)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline,
@@ -86,16 +87,96 @@ DocumentTimeline::ToTimelineTime(const T
   if (MOZ_UNLIKELY(!timing)) {
     return result;
   }
 
   result.SetValue(aTimeStamp - timing->GetNavigationStartTimeStamp());
   return result;
 }
 
+void
+DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation)
+{
+  AnimationTimeline::NotifyAnimationUpdated(aAnimation);
+
+  if (!mIsObservingRefreshDriver) {
+    nsRefreshDriver* refreshDriver = GetRefreshDriver();
+    if (refreshDriver) {
+      refreshDriver->AddRefreshObserver(this, Flush_Style);
+      mIsObservingRefreshDriver = true;
+    }
+  }
+}
+
+void
+DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
+{
+  MOZ_ASSERT(mIsObservingRefreshDriver);
+
+  bool needsTicks = false;
+  AnimationArray animationsToKeep(mAnimationOrder.Length());
+
+  nsAutoAnimationMutationBatch mb(mDocument);
+
+  for (Animation* animation : mAnimationOrder) {
+    // Skip any animations that are longer need associated with this timeline.
+    if (animation->GetTimeline() != this) {
+      mAnimations.RemoveEntry(animation);
+      continue;
+    }
+
+    needsTicks |= animation->NeedsTicks();
+    // Even if |animation| doesn't need future ticks, we should still
+    // Tick it this time around since it might just need a one-off tick in
+    // order to dispatch events.
+    animation->Tick();
+
+    if (animation->IsRelevant() || animation->NeedsTicks()) {
+      animationsToKeep.AppendElement(animation);
+    } else {
+      mAnimations.RemoveEntry(animation);
+    }
+  }
+
+  mAnimationOrder.SwapElements(animationsToKeep);
+
+  if (!needsTicks) {
+    // If another refresh driver observer destroys the nsPresContext,
+    // nsRefreshDriver will detect it and we won't be called.
+    MOZ_ASSERT(GetRefreshDriver(),
+               "Refresh driver should still be valid inside WillRefresh");
+    GetRefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
+    mIsObservingRefreshDriver = false;
+  }
+}
+
+void
+DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver)
+{
+  MOZ_ASSERT(!mIsObservingRefreshDriver,
+             "Timeline should not be observing the refresh driver before"
+             " it is created");
+
+  if (!mAnimationOrder.IsEmpty()) {
+    aDriver->AddRefreshObserver(this, Flush_Style);
+    mIsObservingRefreshDriver = true;
+  }
+}
+
+void
+DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver)
+{
+  if (!mIsObservingRefreshDriver) {
+    return;
+  }
+
+  aDriver->RemoveRefreshObserver(this, Flush_Style);
+  mIsObservingRefreshDriver = false;
+}
+
 TimeStamp
 DocumentTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
 {
   TimeStamp result;
   nsRefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
   if (MOZ_UNLIKELY(!timing)) {
     return result;
   }
--- a/dom/animation/DocumentTimeline.h
+++ b/dom/animation/DocumentTimeline.h
@@ -12,27 +12,34 @@
 #include "nsIDocument.h"
 #include "nsRefreshDriver.h"
 
 struct JSContext;
 
 namespace mozilla {
 namespace dom {
 
-class DocumentTimeline final : public AnimationTimeline
+class DocumentTimeline final
+  : public AnimationTimeline
+  , public nsARefreshObserver
 {
 public:
   explicit DocumentTimeline(nsIDocument* aDocument)
     : AnimationTimeline(aDocument->GetParentObject())
     , mDocument(aDocument)
+    , mIsObservingRefreshDriver(false)
   {
   }
 
 protected:
-  virtual ~DocumentTimeline() { }
+  virtual ~DocumentTimeline()
+  {
+    MOZ_ASSERT(!mIsObservingRefreshDriver, "Timeline should have disassociated"
+               " from the refresh driver before being destroyed");
+  }
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DocumentTimeline,
                                                          AnimationTimeline)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
@@ -45,24 +52,33 @@ public:
     nsRefreshDriver* refreshDriver = GetRefreshDriver();
     return !refreshDriver ||
            !refreshDriver->IsTestControllingRefreshesEnabled();
   }
   Nullable<TimeDuration> ToTimelineTime(const TimeStamp& aTimeStamp) const
                                                                      override;
   TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override;
 
+  void NotifyAnimationUpdated(Animation& aAnimation) override;
+
+  // nsARefreshObserver methods
+  void WillRefresh(TimeStamp aTime) override;
+
+  void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
+  void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
+
 protected:
   TimeStamp GetCurrentTimeStamp() const;
   nsRefreshDriver* GetRefreshDriver() const;
 
   nsCOMPtr<nsIDocument> mDocument;
 
   // The most recently used refresh driver time. This is used in cases where
   // we don't have a refresh driver (e.g. because we are in a display:none
   // iframe).
   mutable TimeStamp mLastRefreshDriverTime;
+  bool mIsObservingRefreshDriver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_DocumentTimeline_h
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -26,8 +26,12 @@ UNIFIED_SOURCES += [
     'AnimationEffectReadOnly.cpp',
     'AnimationTimeline.cpp',
     'DocumentTimeline.cpp',
     'KeyframeEffect.cpp',
     'PendingAnimationTracker.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/dom/base',
+]
--- a/dom/animation/test/css-animations/file_animation-currenttime.html
+++ b/dom/animation/test/css-animations/file_animation-currenttime.html
@@ -436,16 +436,34 @@ async_test(function(t) {
     waitForAnimationFrames(2).then(function() {
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> active, then back');
 
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
+  div.style.animation = ANIM_PROPERTY_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.pause();
+  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(t.step_func(function() {
+    animation.currentTime = currentTimeForActivePhase(animation.timeline);
+    return eventWatcher.wait_for('animationstart');
+  })).then(t.step_func(function() {
+    t.done();
+  }));
+
+}, 'Seeking finished -> paused dispatches animationstart');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var animation = div.getAnimations()[0];
 
   animation.ready.then(t.step_func(function() {
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -354,30 +354,30 @@ AudioChannelService::GetState(nsPIDOMWin
 
     // If there is no parent, or we are the toplevel we don't continue.
   } while (window && window != aWindow);
 }
 
 bool
 AudioChannelService::TelephonyChannelIsActive()
 {
-  nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
-  while (iter.HasMore()) {
-    AudioChannelWindow* next = iter.GetNext();
+  nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows);
+  while (windowsIter.HasMore()) {
+    AudioChannelWindow* next = windowsIter.GetNext();
     if (next->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 &&
         !next->mChannels[(uint32_t)AudioChannel::Telephony].mMuted) {
       return true;
     }
   }
 
   if (IsParentProcess()) {
     nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
-      iter(mPlayingChildren);
-    while (iter.HasMore()) {
-      AudioChannelChildStatus* child = iter.GetNext();
+      childrenIter(mPlayingChildren);
+    while (childrenIter.HasMore()) {
+      AudioChannelChildStatus* child = childrenIter.GetNext();
       if (child->mActiveTelephonyChannel) {
         return true;
       }
     }
   }
 
   return false;
 }
--- a/dom/audiochannel/moz.build
+++ b/dom/audiochannel/moz.build
@@ -19,8 +19,11 @@ EXPORTS += [
 UNIFIED_SOURCES += [
     'AudioChannelAgent.cpp',
     'AudioChannelService.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wshadow']
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6093,17 +6093,17 @@ nsContentTypeParser::GetParameter(const 
                                     aResult);
 }
 
 /* static */
 
 bool
 nsContentUtils::CanAccessNativeAnon()
 {
-  return IsCallerChrome() || IsCallerContentXBL();
+  return LegacyIsCallerChromeOrNativeCode() || IsCallerContentXBL();
 }
 
 /* static */ nsresult
 nsContentUtils::DispatchXULCommand(nsIContent* aTarget,
                                    bool aTrusted,
                                    nsIDOMEvent* aSourceEvent,
                                    nsIPresShell* aShell,
                                    bool aCtrl,
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -123,17 +123,17 @@ static const int64_t kICCSliceBudget = 5
 static const uint32_t kMaxICCDuration = 2000; // ms
 
 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
 // objects in the purple buffer.
 #define NS_CC_FORCED                (2 * 60 * PR_USEC_PER_SEC) // 2 min
 #define NS_CC_FORCED_PURPLE_LIMIT   10
 
 // Don't allow an incremental GC to lock out the CC for too long.
-#define NS_MAX_CC_LOCKEDOUT_TIME    (15 * PR_USEC_PER_SEC) // 15 seconds
+#define NS_MAX_CC_LOCKEDOUT_TIME    (30 * PR_USEC_PER_SEC) // 30 seconds
 
 // Trigger a CC if the purple buffer exceeds this size when we check it.
 #define NS_CC_PURPLE_LIMIT          200
 
 // Large value used to specify that a script should run essentially forever
 #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
 
 // if you add statics here, add them to the list in StartupJSEnvironment
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -669,17 +669,19 @@ CanvasGradient::AddColorStop(float offse
   nsCSSValue value;
   nsCSSParser parser;
   if (!parser.ParseColorString(colorstr, nullptr, 0, value)) {
     rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
   nscolor color;
-  if (!nsRuleNode::ComputeColor(value, nullptr, nullptr, color)) {
+  nsCOMPtr<nsIPresShell> presShell = mContext ? mContext->GetPresShell() : nullptr;
+  if (!nsRuleNode::ComputeColor(value, presShell ? presShell->GetPresContext() : nullptr,
+                                nullptr, color)) {
     rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
   mStops = nullptr;
 
   GradientStop newStop;
 
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -208,16 +208,17 @@ skip-if = toolkit == 'cocoa'
 [test_2d.path.arc.shape.3.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.path.rect.selfintersect.html]
 skip-if = toolkit != 'cocoa'
 # This test is bogus according to the spec; see bug 407107
 [test_2d.path.rect.zero.6.html]
 disabled = bug 407107
 [test_2d.strokeRect.zero.5.html]
+[test_bug232227.html]
 [test_bug613794.html]
 [test_bug753758.html]
 [test_bug764125.html]
 [test_bug856472.html]
 [test_bug866575.html]
 skip-if = (toolkit == 'gonk' && debug) #bug 1045153
 [test_bug902651.html]
 [test_canvas.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_bug232227.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=232227
+-->
+<head>
+  <title>Test for Bug 232227</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=232227">Mozilla Bug 232227</a>
+<!-- CSS system colors -->
+<!--<table cellspacing=0 border=1>
+<caption>CSS system colors</caption>
+<canvas id=colorTestCanvas width=1 height=1 style="display:none"></canvas> -->
+<script type="application/javascript">
+
+/** Test for Bug 232227 **/
+
+function beginTest() {
+	var colorNames = [
+		[ "ActiveBorder", 0xB4, 0xB4, 0xB4 ],
+		[ "ActiveCaption", 0x99, 0xB4, 0xD1 ],
+		[ "AppWorkspace", 0xAB, 0xAB, 0xAB ],
+		[ "Background", 0x00, 0x00, 0x00 ],
+		[ "ButtonFace", 0xF0, 0xF0, 0xF0 ],
+		[ "ButtonHighlight", 0xFF, 0xFF, 0xFF ],
+		[ "ButtonShadow", 0xA0, 0xA0, 0xA0 ],
+		[ "ButtonText", 0x00, 0x00, 0x00 ],
+		[ "CaptionText", 0x00, 0x00, 0x00 ],
+		[ "GrayText", 0x6D, 0x6D, 0x6D ],
+		[ "Highlight", 0x33, 0x99, 0xFF ],
+		[ "HighlightText", 0xFF, 0xFF, 0xFF ],
+		[ "InactiveBorder", 0xF4, 0xF7, 0xFC ],
+		[ "InactiveCaption", 0xBF, 0xCD, 0xDB ],
+		[ "InactiveCaptionText", 0x43, 0x4E, 0x54 ],
+		[ "InfoBackground", 0xFF, 0xFF, 0xE1 ],
+		[ "InfoText", 0x00, 0x00, 0x00 ],
+		[ "Menu", 0xF0, 0xF0, 0xF0 ],
+		[ "MenuText", 0x00, 0x00, 0x00 ],
+		[ "Scrollbar", 0xC8, 0xC8, 0xC8 ],
+		[ "ThreeDDarkShadow", 0x69, 0x69, 0x69 ],
+		[ "ThreeDFace", 0xF0, 0xF0, 0xF0 ],
+		[ "ThreeDHighlight", 0xFF, 0xFF, 0xFF ],
+		[ "ThreeDLightShadow", 0xE3, 0xE3, 0xE3 ],
+		[ "ThreeDShadow", 0xA0, 0xA0, 0xA0 ],
+		[ "Window", 0xFF, 0xFF, 0xFF ],
+		[ "WindowFrame", 0x64, 0x64, 0x64 ],
+		[ "WindowText", 0x00, 0x00, 0x00 ],
+		[ "-moz-ButtonDefault", 0x69, 0x69, 0x69 ],
+		[ "-moz-ButtonHoverFace", 0xF0, 0xF0, 0xF0 ],
+    [ "-moz-ButtonHoverText", 0x00, 0x00, 0x00 ],
+		[ "-moz-CellHighlight", 0xF0, 0xF0, 0xF0 ],
+		[ "-moz-CellHighlightText", 0x00, 0x00, 0x00 ],
+		[ "-moz-Combobox", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-ComboboxText", 0x00, 0x00, 0x00 ],
+		[ "-moz-Dialog", 0xF0, 0xF0, 0xF0 ],
+		[ "-moz-DialogText", 0x00, 0x00, 0x00 ],
+		[ "-moz-DragTargetZone", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-EvenTreeRow", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-Field", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-FieldText", 0x00, 0x00, 0x00 ],
+		[ "-moz-MenuHover", 0x33, 0x99, 0xFF ],
+		[ "-moz-MenuHoverText", 0x00, 0x00, 0x00 ],
+		[ "-moz-MenubarText", 0x00, 0x00, 0x00 ],
+    [ "-moz-MenubarHoverText", 0x00, 0x00, 0x00 ],
+		[ "-moz-NativeHyperlinkText", 0x00, 0x66, 0xCC ],
+		[ "-moz-OddTreeRow", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-html-CellHighlight", 0x33, 0x99, 0xFF ],
+		[ "-moz-html-CellHighlightText", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-mac-chrome-active", 0xB2, 0xB2, 0xB2 ],
+		[ "-moz-mac-chrome-inactive", 0xE1, 0xE1, 0xE1 ],
+		[ "-moz-mac-focusring", 0x60, 0x9D, 0xD7 ],
+		[ "-moz-mac-menuselect", 0x38, 0x75, 0xD7 ],
+		[ "-moz-mac-menushadow", 0xA3, 0xA3, 0xA3 ],
+		[ "-moz-mac-menutextdisable", 0x88, 0x88, 0x88 ],
+		[ "-moz-mac-menutextselect", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-mac-DisabledToolbarText", 0x3F, 0x3F, 0x3F ],
+		[ "-moz-mac-AlternatePrimaryHighlight", 0x3F, 0x3F, 0x3F ],
+		[ "-moz-mac-SecondaryHighlight", 0xD4, 0xD4, 0xD4 ],
+		[ "-moz-win-MediaText", 0xFF, 0xFF, 0xFF ],
+		[ "-moz-win-CommunicationsText", 0xFF, 0xFF, 0xFF ],
+
+		// These five are configured via Tools -> Options -> Content -> Colors.
+		//"-moz-ActiveHyperlinkText",
+		//"-moz-HyperLinkText",
+		//"-moz-VisitedHyperlinkText",
+		//"-moz-default-background-color",
+		//"-moz-default-color",
+	];
+
+  var colorTestCanvas = document.createElement("canvas");
+  colorTestCanvas.width = colorTestCanvas.height = 1;
+  colorTestCanvas = colorTestCanvas.getContext("2d");
+
+	var colorTestDiv = document.createElement("div");
+	document.body.appendChild(colorTestDiv); // ie
+
+	for (var i in colorNames) {
+		(function(colorName, r, g, b) {
+      // test value
+      var ctest = "rgb(" + r + ", " + g + ", " + b + ")";
+
+      // computed value
+      colorTestDiv.style.backgroundColor = "#FF00FE";
+      try {
+        colorTestDiv.style.backgroundColor = colorName;
+      } catch (ex) {} // ie
+      var c1;
+      if (window.getComputedStyle) {
+        c1 = getComputedStyle(colorTestDiv, null).backgroundColor;
+      } else { // ie
+        var range = document.body.createTextRange();
+        range.moveToElementText(colorTestDiv);
+        c1 = range.queryCommandValue("BackColor");
+        c1 = "rgb(" + (c1 & 0xFF) + ", " + ((c1 >> 8) & 0xFF) + ", " + ((c1 >> 16) & 0xFF) + ")";
+      }
+      if (c1 != "rgb(255, 0, 254)") {
+        is(c1, ctest, "Stand-in computed color: " + colorName + " is correct.");
+      }
+
+      // canvas
+      if (colorTestCanvas) {
+        colorTestCanvas.fillStyle = colorName;
+        colorTestCanvas.fillRect(0, 0, 1, 1);
+        var c2 = colorTestCanvas.getImageData(0, 0, 1, 1).data;
+        c2 = "rgb(" + c2[0] + ", " + c2[1] + ", " + c2[2] + ")";
+
+        // combine
+        if (c2 != c1 && c2 != "rgb(0, 0, 0)") {
+          is(c2, ctest, "Stand-in canvas color: " + colorName + " is correct.");
+        }
+      }
+    })(colorNames[i][0], colorNames[i][1], colorNames[i][2], colorNames[i][3]);
+  }
+  SimpleTest.finish();
+}
+
+var prefs = [
+  [ "ui.use_standins_for_native_colors", true ],
+  [ "ui.use_native_colors", true ],
+];
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
+
+</script>
+<!-- </table> -->
+</body>
+</html>
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -514,17 +514,18 @@ DataTransfer::GetMozSourceNode()
   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
   if (!dragSession) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDOMNode> sourceNode;
   dragSession->GetSourceNode(getter_AddRefs(sourceNode));
   nsCOMPtr<nsINode> node = do_QueryInterface(sourceNode);
-  if (node && !nsContentUtils::CanCallerAccess(node)) {
+  if (node && !nsContentUtils::LegacyIsCallerNativeCode()
+      && !nsContentUtils::CanCallerAccess(node)) {
     return nullptr;
   }
 
   return node.forget();
 }
 
 NS_IMETHODIMP
 DataTransfer::GetMozSourceNode(nsIDOMNode** aSourceNode)
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -184,13 +184,12 @@ support-files =
 support-files =
   bug989198_embedded.html
   bug989198_helper.js
 skip-if = buildapp == 'b2g' || e10s
 [test_bug1096146.html]
 support-files =
   bug1096146_embedded.html
 [test_offsetxy.html]
-skip-if = toolkit == 'android' || buildapp == 'b2g' || buildapp == 'mulet'
 [test_eventhandler_scoping.html]
 [test_bug1013412.html]
 skip-if = buildapp == 'b2g' # no wheel events on b2g
 [test_dom_activate_event.html]
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -321,106 +321,87 @@ HTMLLinkElement::UpdatePreconnect()
     nsCOMPtr<nsIURI> uri = GetHrefURI();
     if (uri) {
         owner->MaybePreconnect(uri, GetCORSMode());
     }
   }
 }
 
 nsresult
-HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                         nsIAtom* aPrefix, const nsAString& aValue,
-                         bool aNotify)
+HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                              const nsAttrValue* aValue, bool aNotify)
 {
-  nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
-                                              aValue, aNotify);
-
-  // The ordering of the parent class's SetAttr call and Link::ResetLinkState
-  // is important here!  The attribute is not set until SetAttr returns, and
-  // we will need the updated attribute value because notifying the document
-  // that content states have changed will call IntrinsicState, which will try
-  // to get updated information about the visitedness from Link.
+  // It's safe to call ResetLinkState here because our new attr value has
+  // already been set or unset.  ResetLinkState needs the updated attribute
+  // value because notifying the document that content states have changed will
+  // call IntrinsicState, which will try to get updated information about the
+  // visitedness from Link.
   if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
-    Link::ResetLinkState(!!aNotify, true);
+    bool hasHref = aValue;
+    Link::ResetLinkState(!!aNotify, hasHref);
     if (IsInUncomposedDoc()) {
       CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
     }
   }
 
-  if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None &&
-      (aName == nsGkAtoms::href ||
-       aName == nsGkAtoms::rel ||
-       aName == nsGkAtoms::title ||
-       aName == nsGkAtoms::media ||
-       aName == nsGkAtoms::type)) {
-    bool dropSheet = false;
-    if (aName == nsGkAtoms::rel) {
-      uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(aValue,
-                                                              NodePrincipal());
-      if (GetSheet()) {
-        dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
-      } else if (linkTypes & eHTMLIMPORT) {
+  if (aValue) {
+    if (aNameSpaceID == kNameSpaceID_None &&
+        (aName == nsGkAtoms::href ||
+         aName == nsGkAtoms::rel ||
+         aName == nsGkAtoms::title ||
+         aName == nsGkAtoms::media ||
+         aName == nsGkAtoms::type)) {
+      bool dropSheet = false;
+      if (aName == nsGkAtoms::rel) {
+        nsAutoString value;
+        aValue->ToString(value);
+        uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value,
+                                                                NodePrincipal());
+        if (GetSheet()) {
+          dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
+        } else if (linkTypes & eHTMLIMPORT) {
+          UpdateImport();
+        } else if ((linkTypes & ePRECONNECT) && IsInComposedDoc()) {
+          UpdatePreconnect();
+        }
+      }
+
+      if (aName == nsGkAtoms::href) {
         UpdateImport();
-      } else if ((linkTypes & ePRECONNECT) && IsInComposedDoc()) {
-        UpdatePreconnect();
+        if (IsInComposedDoc()) {
+          UpdatePreconnect();
+        }
       }
-    }
 
-    if (aName == nsGkAtoms::href) {
-      UpdateImport();
-      if (IsInComposedDoc()) {
-        UpdatePreconnect();
-      }
+      UpdateStyleSheetInternal(nullptr, nullptr,
+                               dropSheet ||
+                               (aName == nsGkAtoms::title ||
+                                aName == nsGkAtoms::media ||
+                                aName == nsGkAtoms::type));
     }
-    
-    UpdateStyleSheetInternal(nullptr, nullptr,
-                             dropSheet ||
-                             (aName == nsGkAtoms::title ||
-                              aName == nsGkAtoms::media ||
-                              aName == nsGkAtoms::type));
-  }
-
-  return rv;
-}
-
-nsresult
-HTMLLinkElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                           bool aNotify)
-{
-  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
-                                                aNotify);
-  // Since removing href or rel makes us no longer link to a
-  // stylesheet, force updates for those too.
-  if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None) {
-    if (aAttribute == nsGkAtoms::href ||
-        aAttribute == nsGkAtoms::rel ||
-        aAttribute == nsGkAtoms::title ||
-        aAttribute == nsGkAtoms::media ||
-        aAttribute == nsGkAtoms::type) {
-      UpdateStyleSheetInternal(nullptr, nullptr, true);
-    }
-    if (aAttribute == nsGkAtoms::href ||
-        aAttribute == nsGkAtoms::rel) {
-      UpdateImport();
+  } else {
+    // Since removing href or rel makes us no longer link to a
+    // stylesheet, force updates for those too.
+    if (aNameSpaceID == kNameSpaceID_None) {
+      if (aName == nsGkAtoms::href ||
+          aName == nsGkAtoms::rel ||
+          aName == nsGkAtoms::title ||
+          aName == nsGkAtoms::media ||
+          aName == nsGkAtoms::type) {
+        UpdateStyleSheetInternal(nullptr, nullptr, true);
+      }
+      if (aName == nsGkAtoms::href ||
+          aName == nsGkAtoms::rel) {
+        UpdateImport();
+      }
     }
   }
 
-  // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
-  // is important here!  The attribute is not unset until UnsetAttr returns, and
-  // we will need the updated attribute value because notifying the document
-  // that content states have changed will call IntrinsicState, which will try
-  // to get updated information about the visitedness from Link.
-  if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
-    Link::ResetLinkState(!!aNotify, false);
-    if (IsInUncomposedDoc()) {
-      CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
-    }
-  }
-
-  return rv;
+  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+                                            aNotify);
 }
 
 nsresult
 HTMLLinkElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
   return PreHandleEventForAnchors(aVisitor);
 }
 
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -56,26 +56,19 @@ public:
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
-  nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                   const nsAString& aValue, bool aNotify)
-  {
-    return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
-  }
-  virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                           nsIAtom* aPrefix, const nsAString& aValue,
-                           bool aNotify) override;
-  virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                             bool aNotify) override;
+  virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                bool aNotify) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
   // Element
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
                               const nsAString& aValue,
                               nsAttrValue& aResult) override;
--- a/dom/html/HTMLStyleElement.cpp
+++ b/dom/html/HTMLStyleElement.cpp
@@ -167,52 +167,32 @@ HTMLStyleElement::UnbindFromTree(bool aD
     // do not need to be updated.
     return;
   }
 
   UpdateStyleSheetInternal(oldDoc, oldShadow);
 }
 
 nsresult
-HTMLStyleElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                          nsIAtom* aPrefix, const nsAString& aValue,
-                          bool aNotify)
+HTMLStyleElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                               const nsAttrValue* aValue, bool aNotify)
 {
-  nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
-                                              aValue, aNotify);
-  if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None) {
+  if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::title ||
         aName == nsGkAtoms::media ||
         aName == nsGkAtoms::type) {
       UpdateStyleSheetInternal(nullptr, nullptr, true);
     } else if (aName == nsGkAtoms::scoped) {
-      UpdateStyleSheetScopedness(true);
+      bool isScoped = aValue;
+      UpdateStyleSheetScopedness(isScoped);
     }
   }
 
-  return rv;
-}
-
-nsresult
-HTMLStyleElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                            bool aNotify)
-{
-  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
-                                                aNotify);
-  if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None) {
-    if (aAttribute == nsGkAtoms::title ||
-        aAttribute == nsGkAtoms::media ||
-        aAttribute == nsGkAtoms::type) {
-      UpdateStyleSheetInternal(nullptr, nullptr, true);
-    } else if (aAttribute == nsGkAtoms::scoped) {
-      UpdateStyleSheetScopedness(false);
-    }
-  }
-
-  return rv;
+  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+                                            aNotify);
 }
 
 NS_IMETHODIMP
 HTMLStyleElement::GetInnerHTML(nsAString& aInnerHTML)
 {
   if (!nsContentUtils::GetNodeTextContent(this, false, aInnerHTML, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/html/HTMLStyleElement.h
+++ b/dom/html/HTMLStyleElement.h
@@ -41,26 +41,19 @@ public:
   // nsIDOMHTMLStyleElement
   NS_DECL_NSIDOMHTMLSTYLEELEMENT
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
-  nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                   const nsAString& aValue, bool aNotify)
-  {
-    return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
-  }
-  virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                           nsIAtom* aPrefix, const nsAString& aValue,
-                           bool aNotify) override;
-  virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                             bool aNotify) override;
+  virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                bool aNotify) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -432,16 +432,19 @@ ConsoleListener::Observe(nsIConsoleMessa
         rv = scriptError->GetSourceLine(sourceLine);
         NS_ENSURE_SUCCESS(rv, rv);
 
         // Before we send the error to the parent process (which
         // involves copying the memory), truncate any long lines.  CSS
         // errors in particular share the memory for long lines with
         // repeated errors, but the IPC communication we're about to do
         // will break that sharing, so we better truncate now.
+        if (sourceName.Length() > 1000) {
+            sourceName.Truncate(1000);
+        }
         if (sourceLine.Length() > 1000) {
             sourceLine.Truncate(1000);
         }
 
         rv = scriptError->GetCategory(getter_Copies(category));
         NS_ENSURE_SUCCESS(rv, rv);
         rv = scriptError->GetLineNumber(&lineNum);
         NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -382,18 +382,16 @@ parent:
      * @param force
      *   Invalidate any locally cached cursor settings and force an
      *   update.
      */
     SetCustomCursor(nsCString cursorData, uint32_t width, uint32_t height,
                     uint32_t stride, uint8_t format,
                     uint32_t hotspotX, uint32_t hotspotY, bool force);
 
-    SetBackgroundColor(nscolor color);
-
     /**
      * Used to set the current text of the status tooltip.
      * Nowadays this is mainly used for link locations on hover.
      */
     SetStatus(uint32_t type, nsString status);
 
     /**
      * Show/hide a tooltip when the mouse hovers over an element in the content
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -608,17 +608,16 @@ TabChild::TabChild(nsIContentChild* aMan
   : TabContext(aContext)
   , mRemoteFrame(nullptr)
   , mManager(aManager)
   , mChromeFlags(aChromeFlags)
   , mActiveSuppressDisplayport(0)
   , mLayersId(0)
   , mActivePointerId(-1)
   , mAppPackageFileDescriptorRecved(false)
-  , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mDidFakeShow(false)
   , mNotified(false)
   , mTriedBrowserInit(false)
   , mOrientation(eScreenOrientation_PortraitPrimary)
   , mUpdateHitRegion(false)
   , mIgnoreKeyPressEvent(false)
   , mHasValidInnerSize(false)
   , mDestroyed(false)
@@ -2740,25 +2739,16 @@ TabChild::InitRenderingState(const Textu
         observerService->AddObserver(this,
                                      BEFORE_FIRST_PAINT,
                                      false);
     }
     return true;
 }
 
 void
-TabChild::SetBackgroundColor(const nscolor& aColor)
-{
-  if (mLastBackgroundColor != aColor) {
-    mLastBackgroundColor = aColor;
-    SendSetBackgroundColor(mLastBackgroundColor);
-  }
-}
-
-void
 TabChild::GetDPI(float* aDPI)
 {
     *aDPI = -1.0;
     if (!mRemoteFrame) {
         return;
     }
 
     if (mDPI > 0) {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1995,25 +1995,16 @@ TabParent::RecvSetCustomCursor(const nsC
       mCustomCursorHotspotX = aHotspotX;
       mCustomCursorHotspotY = aHotspotY;
     }
   }
 
   return true;
 }
 
-bool
-TabParent::RecvSetBackgroundColor(const nscolor& aColor)
-{
-  if (RenderFrameParent* frame = GetRenderFrame()) {
-    frame->SetBackgroundColor(aColor);
-  }
-  return true;
-}
-
 nsIXULBrowserWindow*
 TabParent::GetXULBrowserWindow()
 {
   nsCOMPtr<nsIContent> frame = do_QueryInterface(mFrameElement);
   if (!frame) {
     return nullptr;
   }
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -210,17 +210,16 @@ public:
     virtual bool RecvSetCustomCursor(const nsCString& aUri,
                                      const uint32_t& aWidth,
                                      const uint32_t& aHeight,
                                      const uint32_t& aStride,
                                      const uint8_t& aFormat,
                                      const uint32_t& aHotspotX,
                                      const uint32_t& aHotspotY,
                                      const bool& aForce) override;
-    virtual bool RecvSetBackgroundColor(const nscolor& aValue) override;
     virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus) override;
     virtual bool RecvIsParentWindowMainWidgetVisible(bool* aIsVisible) override;
     virtual bool RecvShowTooltip(const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip) override;
     virtual bool RecvHideTooltip() override;
     virtual bool RecvGetTabOffset(LayoutDeviceIntPoint* aPoint) override;
     virtual bool RecvGetDPI(float* aValue) override;
     virtual bool RecvGetDefaultScale(double* aValue) override;
     virtual bool RecvGetMaxTouchPoints(uint32_t* aTouchPoints) override;
--- a/dom/ipc/nsIContentParent.cpp
+++ b/dom/ipc/nsIContentParent.cpp
@@ -123,19 +123,45 @@ nsIContentParent::AllocPBrowserParent(co
   unused << aCpId;
   unused << aIsForApp;
   unused << aIsForBrowser;
 
   if (!CanOpenBrowser(aContext)) {
     return nullptr;
   }
 
+  const IPCTabAppBrowserContext& appBrowser = aContext.appBrowserContext();
+  const PopupIPCTabContext& popupContext = appBrowser.get_PopupIPCTabContext();
+
+  uint32_t chromeFlags = aChromeFlags;
+
+  // CanOpenBrowser has ensured that the IPCTabContext is of
+  // type PopupIPCTabContext, and that the opener TabParent is
+  // reachable.
+  auto opener = TabParent::GetFrom(popupContext.opener().get_PBrowserParent());
+  // We must ensure that the private browsing and remoteness flags
+  // match those of the opener.
+  nsCOMPtr<nsILoadContext> loadContext = opener->GetLoadContext();
+  if (!loadContext) {
+    return nullptr;
+  }
+
+  bool isPrivate;
+  loadContext->GetUsePrivateBrowsing(&isPrivate);
+  if (isPrivate) {
+    chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+  }
+
+  // And because we're allocating a remote browser, of course the
+  // window is remote.
+  chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
+
   MaybeInvalidTabContext tc(aContext);
   MOZ_ASSERT(tc.IsValid());
-  TabParent* parent = new TabParent(this, aTabId, tc.GetTabContext(), aChromeFlags);
+  TabParent* parent = new TabParent(this, aTabId, tc.GetTabContext(), chromeFlags);
 
   // We release this ref in DeallocPBrowserParent()
   NS_ADDREF(parent);
   return parent;
 }
 
 bool
 nsIContentParent::DeallocPBrowserParent(PBrowserParent* aFrame)
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -344,18 +344,17 @@ IsDirectShowSupportedType(const nsACStri
 
 #ifdef MOZ_FMP4
 static bool
 IsMP4SupportedType(const nsACString& aType,
                    const nsAString& aCodecs = EmptyString())
 {
   // MP4Decoder/Reader is currently used for MSE and mp4 files local playback.
   bool haveAAC, haveMP3, haveH264;
-  return Preferences::GetBool("media.fragmented-mp4.exposed", false) &&
-         MP4Decoder::CanHandleMediaType(aType, aCodecs, haveAAC, haveH264, haveMP3);
+  return MP4Decoder::CanHandleMediaType(aType, aCodecs, haveAAC, haveH264, haveMP3);
 }
 #endif
 
 /* static */ bool
 DecoderTraits::IsMP4Type(const nsACString& aType)
 {
 #ifdef MOZ_FMP4
   return IsMP4SupportedType(aType);
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -8,17 +8,16 @@
 
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include "AudioBufferUtils.h"
 #include "AudioMixer.h"
 #include "AudioSegment.h"
 #include "SelfRef.h"
 #include "mozilla/Atomics.h"
-#include "AudioContext.h"
 
 struct cubeb_stream;
 
 template <>
 class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream>
 {
 public:
   static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -683,17 +683,17 @@ FrameParser::FrameHeader::Padding() cons
 
 uint8_t
 FrameParser::FrameHeader::Private() const {
   return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE];
 }
 
 uint8_t
 FrameParser::FrameHeader::RawChannelMode() const {
-  return 0xF & mRaw[frame_header::CHANNELMODE_MODEEXT_COPY_ORIG_EMPH] >> 4;
+  return 0x3 & mRaw[frame_header::CHANNELMODE_MODEEXT_COPY_ORIG_EMPH] >> 6;
 }
 
 int32_t
 FrameParser::FrameHeader::Layer() const {
   static const uint8_t LAYERS[4] = { 0, 3, 2, 1 };
 
   return LAYERS[RawLayer()];
 }
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -309,18 +309,20 @@ public:
           if (mAudioDevice) {
             mAudioDevice->GetSource()->Stop(source, kAudioTrack);
             mAudioDevice->GetSource()->Deallocate();
           }
           if (mVideoDevice) {
             mVideoDevice->GetSource()->Stop(source, kVideoTrack);
             mVideoDevice->GetSource()->Deallocate();
           }
-          // Do this after stopping all tracks with EndTrack()
-          if (mBool) {
+          // We consider ourselves finished if all tracks have been stopped, as
+          // there is no way to restart them from the JS APIs.
+          if (mBool || ((!mAudioDevice || mAudioDevice->GetSource()->IsAvailable()) &&
+                        (!mVideoDevice || mVideoDevice->GetSource()->IsAvailable()))) {
             source->Finish();
           }
 
           nsIRunnable *event =
             new GetUserMediaNotificationEvent(mListener,
                                               mType == MEDIA_STOP ?
                                               GetUserMediaNotificationEvent::STOPPING :
                                               GetUserMediaNotificationEvent::STOPPED_TRACK,
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -175,24 +175,26 @@ public:
                           const dom::MediaTrackConstraints& aConstraints);
 
   // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
   // if set and represent a real capture device.
   bool CapturingVideo()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     return mVideoDevice && !mStopped &&
+           !mVideoDevice->GetSource()->IsAvailable() &&
            mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
            (!mVideoDevice->GetSource()->IsFake() ||
             Preferences::GetBool("media.navigator.permission.fake"));
   }
   bool CapturingAudio()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     return mAudioDevice && !mStopped &&
+           !mAudioDevice->GetSource()->IsAvailable() &&
            (!mAudioDevice->GetSource()->IsFake() ||
             Preferences::GetBool("media.navigator.permission.fake"));
   }
   bool CapturingScreen()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     return mVideoDevice && !mStopped &&
            !mVideoDevice->GetSource()->IsAvailable() &&
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -2906,19 +2906,25 @@ MediaStreamGraphImpl::AudioContextOperat
 {
   // This can be called from the thread created to do cubeb operation, or the
   // MSG thread. The pointers passed back here are refcounted, so are still
   // alive.
   MonitorAutoLock lock(mMonitor);
 
   AudioContextState state;
   switch (aOperation) {
-    case Suspend: state = AudioContextState::Suspended; break;
-    case Resume: state = AudioContextState::Running; break;
-    case Close: state = AudioContextState::Closed; break;
+    case AudioContextOperation::Suspend:
+      state = AudioContextState::Suspended;
+      break;
+    case AudioContextOperation::Resume:
+      state = AudioContextState::Running;
+      break;
+    case AudioContextOperation::Close:
+      state = AudioContextState::Closed;
+      break;
     default: MOZ_CRASH("Not handled.");
   }
 
   nsCOMPtr<nsIRunnable> event = new dom::StateChangeTask(
       aStream->AsAudioNodeStream(), aPromise, state);
   NS_DispatchToMainThread(event.forget());
 }
 
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -18,31 +18,34 @@
 #include "nsIRunnable.h"
 #include "StreamBuffer.h"
 #include "VideoFrameContainer.h"
 #include "VideoSegment.h"
 #include "MainThreadUtils.h"
 #include "nsAutoRef.h"
 #include <speex/speex_resampler.h>
 #include "DOMMediaStream.h"
-#include "AudioContext.h"
 
 class nsIRunnable;
 
 template <>
 class nsAutoRefTraits<SpeexResamplerState> : public nsPointerRefTraits<SpeexResamplerState>
 {
   public:
   static void Release(SpeexResamplerState* aState) { speex_resampler_destroy(aState); }
 };
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaStreamGraphLog;
 
+namespace dom {
+  enum class AudioContextOperation;
+}
+
 /*
  * MediaStreamGraph is a framework for synchronized audio/video processing
  * and playback. It is designed to be used by other browser components such as
  * HTML media elements, media capture APIs, real-time media streaming APIs,
  * multitrack media APIs, and advanced audio APIs.
  *
  * The MediaStreamGraph uses a dedicated thread to process media --- the media
  * graph thread. This ensures that we can process media through the graph
--- a/dom/media/directshow/DirectShowDecoder.cpp
+++ b/dom/media/directshow/DirectShowDecoder.cpp
@@ -40,17 +40,18 @@ DirectShowDecoder::GetSupportedCodecs(co
 
   return false;
 }
 
 /* static */
 bool
 DirectShowDecoder::IsEnabled()
 {
-  return Preferences::GetBool("media.directshow.enabled");
+  return CanDecodeMP3UsingDirectShow() &&
+         Preferences::GetBool("media.directshow.enabled");
 }
 
 DirectShowDecoder::DirectShowDecoder()
 {
   MOZ_COUNT_CTOR(DirectShowDecoder);
 }
 
 DirectShowDecoder::~DirectShowDecoder()
--- a/dom/media/directshow/DirectShowUtils.cpp
+++ b/dom/media/directshow/DirectShowUtils.cpp
@@ -203,20 +203,18 @@ CreateAndAddFilter(IGraphBuilder* aGraph
   }
 
   filter.forget(aOutFilter);
 
   return S_OK;
 }
 
 HRESULT
-AddMP3DMOWrapperFilter(IGraphBuilder* aGraph,
-                       IBaseFilter **aOutFilter)
+CreateMP3DMOWrapperFilter(IBaseFilter **aOutFilter)
 {
-  NS_ENSURE_TRUE(aGraph, E_POINTER);
   NS_ENSURE_TRUE(aOutFilter, E_POINTER);
   HRESULT hr;
 
   // Create the wrapper filter.
   nsRefPtr<IBaseFilter> filter;
   hr = CoCreateInstance(CLSID_DMOWrapperFilter,
                         nullptr,
                         CLSCTX_INPROC_SERVER,
@@ -240,28 +238,75 @@ AddMP3DMOWrapperFilter(IGraphBuilder* aG
   if (FAILED(hr)) {
     // Can't instantiate MP3 DMO. It doesn't exist on Windows XP, we're
     // probably hitting that. Don't log warning to console, this is an
     // expected error.
     WARN("dmoWrapper Init failed, hr=%x", hr);
     return hr;
   }
 
+  filter.forget(aOutFilter);
+
+  return S_OK;
+}
+
+HRESULT
+AddMP3DMOWrapperFilter(IGraphBuilder* aGraph,
+                       IBaseFilter **aOutFilter)
+{
+  NS_ENSURE_TRUE(aGraph, E_POINTER);
+  NS_ENSURE_TRUE(aOutFilter, E_POINTER);
+  HRESULT hr;
+
+  // Create the wrapper filter.
+  nsRefPtr<IBaseFilter> filter;
+  hr = CreateMP3DMOWrapperFilter(getter_AddRefs(filter));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
   // Add the wrapper filter to graph.
   hr = aGraph->AddFilter(filter, L"MP3 Decoder DMO");
   if (FAILED(hr)) {
     WARN("AddFilter failed, hr=%x", hr);
     return hr;
   }
 
   filter.forget(aOutFilter);
 
   return S_OK;
 }
 
+bool
+CanDecodeMP3UsingDirectShow()
+{
+  nsRefPtr<IBaseFilter> filter;
+
+  // Can we create the MP3 demuxer filter?
+  if (FAILED(CoCreateInstance(CLSID_MPEG1Splitter,
+                              nullptr,
+                              CLSCTX_INPROC_SERVER,
+                              IID_IBaseFilter,
+                              getter_AddRefs(filter)))) {
+    return false;
+  }
+
+  // Can we create either the WinXP MP3 decoder filter or the MP3 DMO decoder?
+  if (FAILED(CoCreateInstance(CLSID_MPEG_LAYER_3_DECODER_FILTER,
+                              nullptr,
+                              CLSCTX_INPROC_SERVER,
+                              IID_IBaseFilter,
+                              getter_AddRefs(filter))) &&
+      FAILED(CreateMP3DMOWrapperFilter(getter_AddRefs(filter)))) {
+    return false;
+  }
+
+  // Else, we can create all of the components we need. Assume
+  // DirectShow is going to work...
+  return true;
+}
+
 // Match a pin by pin direction and connection state.
 HRESULT
 MatchUnconnectedPin(IPin* aPin,
                     PIN_DIRECTION aPinDir,
                     bool *aOutMatches)
 {
   NS_ENSURE_TRUE(aPin, E_POINTER);
   NS_ENSURE_TRUE(aOutMatches, E_POINTER);
--- a/dom/media/directshow/DirectShowUtils.h
+++ b/dom/media/directshow/DirectShowUtils.h
@@ -110,11 +110,16 @@ inline double
 RefTimeToSeconds(const REFERENCE_TIME aRefTime)
 {
   return double(aRefTime) / 10000000;
 }
 
 const char*
 GetDirectShowGuidName(const GUID& aGuid);
 
+// Returns true if we can instantiate an MP3 demuxer and decoder filters.
+// Use this to detect whether MP3 support is installed.
+bool
+CanDecodeMP3UsingDirectShow();
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -20,16 +20,20 @@
 #include "mozilla/WindowsVersion.h"
 #endif
 #ifdef MOZ_WIDGET_ANDROID
 #include "nsIGfxInfo.h"
 #include "AndroidBridge.h"
 #endif
 #include "mozilla/layers/LayersTypes.h"
 
+#ifdef MOZ_FFMPEG
+#include "FFmpegRuntimeLinker.h"
+#endif
+
 namespace mozilla {
 
 #if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG)
 #define MP4_READER_DORMANT_HEURISTIC
 #else
 #undef MP4_READER_DORMANT_HEURISTIC
 #endif
 
@@ -179,17 +183,18 @@ MP4Decoder::CanHandleMediaType(const nsA
 }
 
 static bool
 IsFFmpegAvailable()
 {
 #ifndef MOZ_FFMPEG
   return false;
 #else
-  return Preferences::GetBool("media.fragmented-mp4.ffmpeg.enabled", false);
+  nsRefPtr<PlatformDecoderModule> m = FFmpegRuntimeLinker::CreateDecoderModule();
+  return !!m;
 #endif
 }
 
 static bool
 IsAppleAvailable()
 {
 #ifndef MOZ_APPLEMEDIA
   // Not the right platform.
@@ -270,17 +275,17 @@ CreateTestH264Decoder(layers::LayersBack
   aConfig.mImage = nsIntRect(0, 0, 64, 64);
   aConfig.mExtraData = new MediaByteBuffer();
   aConfig.mExtraData->AppendElements(sTestH264ExtraData,
                                      MOZ_ARRAY_LENGTH(sTestH264ExtraData));
 
   PlatformDecoderModule::Init();
 
   nsRefPtr<PlatformDecoderModule> platform = PlatformDecoderModule::Create();
-  if (!platform) {
+  if (!platform || !platform->SupportsMimeType(NS_LITERAL_CSTRING("video/mp4"))) {
     return nullptr;
   }
 
   nsRefPtr<MediaDataDecoder> decoder(
     platform->CreateDecoder(aConfig, nullptr, nullptr, aBackend, nullptr));
   if (!decoder) {
     return nullptr;
   }
@@ -326,17 +331,17 @@ MP4Decoder::CanCreateH264Decoder()
 
 #ifdef XP_WIN
 static already_AddRefed<MediaDataDecoder>
 CreateTestAACDecoder(AudioInfo& aConfig)
 {
   PlatformDecoderModule::Init();
 
   nsRefPtr<PlatformDecoderModule> platform = PlatformDecoderModule::Create();
-  if (!platform) {
+  if (!platform || !platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mp4a-latm"))) {
     return nullptr;
   }
 
   nsRefPtr<MediaDataDecoder> decoder(
     platform->CreateDecoder(aConfig, nullptr, nullptr));
   if (!decoder) {
     return nullptr;
   }
--- a/dom/media/gmp/GMPLoader.cpp
+++ b/dom/media/gmp/GMPLoader.cpp
@@ -130,16 +130,27 @@ GetStackAfterCurrentFrame(uint8_t** aOut
     bottom = (uint8_t*)memInfo.BaseAddress - 1;
   }
   *aOutTop = top;
   *aOutBottom = bottom;
   return true;
 }
 #endif
 
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+static void SecureMemset(void* start, uint8_t value, size_t size)
+{
+  // Inline instructions equivalent to RtlSecureZeroMemory().
+  for (size_t i = 0; i < size; ++i) {
+    volatile uint8_t* p = static_cast<volatile uint8_t*>(start) + i;
+    *p = value;
+  }
+}
+#endif
+
 // The RAII variable holding the activation context that we create before
 // lowering the sandbox is getting optimized out.
 #if defined(_MSC_VER)
 #pragma optimize("g", off)
 #endif
 bool
 GMPLoaderImpl::Load(const char* aUTF8LibPath,
                     uint32_t aUTF8LibPathLen,
@@ -163,20 +174,20 @@ GMPLoaderImpl::Load(const char* aUTF8Lib
     SHA256_Update(&ctx, (const uint8_t*)&volumeId, sizeof(int));
     uint8_t digest[SHA256_LENGTH] = {0};
     unsigned int digestLen = 0;
     SHA256_End(&ctx, digest, &digestLen, SHA256_LENGTH);
 
     // Overwrite all data involved in calculation as it could potentially
     // identify the user, so there's no chance a GMP can read it and use
     // it for identity tracking.
-    memset(&ctx, 0, sizeof(ctx));
-    memset(aOriginSalt, 0, aOriginSaltLen);
-    volumeId = 0;
-    memset(&deviceId[0], '*', sizeof(string16::value_type) * deviceId.size());
+    SecureMemset(&ctx, 0, sizeof(ctx));
+    SecureMemset(aOriginSalt, 0, aOriginSaltLen);
+    SecureMemset(&volumeId, 0, sizeof(volumeId));
+    SecureMemset(&deviceId[0], '*', sizeof(string16::value_type) * deviceId.size());
     deviceId = L"";
 
     if (!rlz_lib::BytesToString(digest, SHA256_LENGTH, &nodeId)) {
       return false;
     }
 
     if (!PR_GetEnv("MOZ_GMP_DISABLE_NODE_ID_CLEANUP")) {
       // We've successfully bound the origin salt to node id.
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -600,55 +600,55 @@ DecodedStream::InitTracks()
   mData->mStreamInitialized = true;
 }
 
 static void
 SendStreamAudio(DecodedStreamData* aStream, int64_t aStartTime,
                 MediaData* aData, AudioSegment* aOutput,
                 uint32_t aRate, double aVolume)
 {
+  // The amount of audio frames that is used to fuzz rounding errors.
+  static const int64_t AUDIO_FUZZ_FRAMES = 1;
+
   MOZ_ASSERT(aData);
   AudioData* audio = aData->As<AudioData>();
   // This logic has to mimic AudioSink closely to make sure we write
   // the exact same silences
   CheckedInt64 audioWrittenOffset = aStream->mAudioFramesWritten +
                                     UsecsToFrames(aStartTime, aRate);
   CheckedInt64 frameOffset = UsecsToFrames(audio->mTime, aRate);
 
   if (!audioWrittenOffset.isValid() ||
       !frameOffset.isValid() ||
       // ignore packet that we've already processed
-      frameOffset.value() + audio->mFrames <= audioWrittenOffset.value()) {
+      audio->GetEndTime() <= aStream->mNextAudioTime) {
     return;
   }
 
-  if (audioWrittenOffset.value() < frameOffset.value()) {
+  if (audioWrittenOffset.value() + AUDIO_FUZZ_FRAMES < frameOffset.value()) {
     int64_t silentFrames = frameOffset.value() - audioWrittenOffset.value();
     // Write silence to catch up
     AudioSegment silence;
     silence.InsertNullDataAtStart(silentFrames);
     aStream->mAudioFramesWritten += silentFrames;
     audioWrittenOffset += silentFrames;
     aOutput->AppendFrom(&silence);
   }
 
-  MOZ_ASSERT(audioWrittenOffset.value() >= frameOffset.value());
-
-  int64_t offset = audioWrittenOffset.value() - frameOffset.value();
-  size_t framesToWrite = audio->mFrames - offset;
-
+  // Always write the whole sample without truncation to be consistent with
+  // DecodedAudioDataSink::PlayFromAudioQueue()
   audio->EnsureAudioBuffer();
   nsRefPtr<SharedBuffer> buffer = audio->mAudioBuffer;
   AudioDataValue* bufferData = static_cast<AudioDataValue*>(buffer->Data());
   nsAutoTArray<const AudioDataValue*, 2> channels;
   for (uint32_t i = 0; i < audio->mChannels; ++i) {
-    channels.AppendElement(bufferData + i * audio->mFrames + offset);
+    channels.AppendElement(bufferData + i * audio->mFrames);
   }
-  aOutput->AppendFrames(buffer.forget(), channels, framesToWrite);
-  aStream->mAudioFramesWritten += framesToWrite;
+  aOutput->AppendFrames(buffer.forget(), channels, audio->mFrames);
+  aStream->mAudioFramesWritten += audio->mFrames;
   aOutput->ApplyVolume(aVolume);
 
   aStream->mNextAudioTime = audio->GetEndTime();
 }
 
 void
 DecodedStream::SendAudio(double aVolume, bool aIsSameOrigin)
 {
--- a/dom/media/platforms/PlatformDecoderModule.cpp
+++ b/dom/media/platforms/PlatformDecoderModule.cpp
@@ -168,21 +168,19 @@ PlatformDecoderModule::CreatePDM()
   if (sUseBlankDecoder) {
     return CreateBlankDecoderModule();
   }
 #ifdef XP_WIN
   nsRefPtr<PlatformDecoderModule> m(new WMFDecoderModule());
   return m.forget();
 #endif
 #ifdef MOZ_FFMPEG
-  if (sFFmpegDecoderEnabled) {
-    nsRefPtr<PlatformDecoderModule> m = FFmpegRuntimeLinker::CreateDecoderModule();
-    if (m) {
-      return m.forget();
-    }
+  nsRefPtr<PlatformDecoderModule> mffmpeg = FFmpegRuntimeLinker::CreateDecoderModule();
+  if (mffmpeg) {
+    return mffmpeg.forget();
   }
 #endif
 #ifdef MOZ_APPLEMEDIA
   nsRefPtr<PlatformDecoderModule> m(new AppleDecoderModule());
   return m.forget();
 #endif
 #ifdef MOZ_GONK_MEDIACODEC
   if (sGonkDecoderEnabled) {
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -27,23 +27,28 @@ FFmpegDataDecoder<LIBAV_VER>::FFmpegData
   : mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
   , mCodecContext(nullptr)
   , mFrame(NULL)
   , mExtraData(nullptr)
   , mCodecID(aCodecID)
   , mMonitor("FFMpegaDataDecoder")
   , mIsFlushing(false)
+  , mCodecParser(nullptr)
 {
   MOZ_COUNT_CTOR(FFmpegDataDecoder);
 }
 
 FFmpegDataDecoder<LIBAV_VER>::~FFmpegDataDecoder()
 {
   MOZ_COUNT_DTOR(FFmpegDataDecoder);
+  if (mCodecParser) {
+    av_parser_close(mCodecParser);
+    mCodecParser = nullptr;
+  }
 }
 
 /**
  * FFmpeg calls back to this function with a list of pixel formats it supports.
  * We choose a pixel format that we support and return it.
  * For now, we just look for YUV420P as it is the only non-HW accelerated format
  * supported by FFmpeg's H264 decoder.
  */
@@ -127,16 +132,21 @@ FFmpegDataDecoder<LIBAV_VER>::InitDecode
       mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLT &&
       mCodecContext->sample_fmt != AV_SAMPLE_FMT_FLTP &&
       mCodecContext->sample_fmt != AV_SAMPLE_FMT_S16 &&
       mCodecContext->sample_fmt != AV_SAMPLE_FMT_S16P) {
     NS_WARNING("FFmpeg audio decoder outputs unsupported audio format.");
     return NS_ERROR_FAILURE;
   }
 
+  mCodecParser = av_parser_init(mCodecID);
+  if (mCodecParser) {
+    mCodecParser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
+  }
+
   FFMPEG_LOG("FFmpeg init successful.");
   return NS_OK;
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Shutdown()
 {
   if (mTaskQueue) {
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
@@ -54,16 +54,17 @@ protected:
 
   // For wait on mIsFlushing during Shutdown() process.
   // Protects mReorderQueue.
   Monitor mMonitor;
   // Set on reader/decode thread calling Flush() to indicate that output is
   // not required and so input samples on mTaskQueue need not be processed.
   // Cleared on mTaskQueue in ProcessDrain().
   Atomic<bool> mIsFlushing;
+  AVCodecParserContext* mCodecParser;
 
 private:
   static bool sFFmpegInitDone;
   static StaticMutex sMonitor;
 };
 
 } // namespace mozilla
 
--- a/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
@@ -16,17 +16,23 @@ namespace mozilla
 
 template <int V>
 class FFmpegDecoderModule : public PlatformDecoderModule
 {
 public:
   static already_AddRefed<PlatformDecoderModule>
   Create()
   {
+    uint32_t major, minor;
+    GetVersion(major, minor);
+    if (major < 54 && !sFFmpegDecoderEnabled) {
+      return nullptr;
+    }
     nsRefPtr<PlatformDecoderModule> pdm = new FFmpegDecoderModule();
+
     return pdm.forget();
   }
 
   static bool
   GetVersion(uint32_t& aMajor, uint32_t& aMinor)
   {
     uint32_t version = avcodec_version();
     aMajor = (version >> 16) & 0xff;
--- a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp
@@ -29,17 +29,16 @@ FFmpegH264Decoder<LIBAV_VER>::FFmpegH264
   const VideoInfo& aConfig,
   ImageContainer* aImageContainer)
   : FFmpegDataDecoder(aTaskQueue, aCallback, GetCodecId(aConfig.mMimeType))
   , mImageContainer(aImageContainer)
   , mPictureWidth(aConfig.mImage.width)
   , mPictureHeight(aConfig.mImage.height)
   , mDisplayWidth(aConfig.mDisplay.width)
   , mDisplayHeight(aConfig.mDisplay.height)
-  , mCodecParser(nullptr)
 {
   MOZ_COUNT_CTOR(FFmpegH264Decoder);
   // Use a new MediaByteBuffer as the object will be modified during initialization.
   mExtraData = new MediaByteBuffer;
   mExtraData->AppendElements(*aConfig.mExtraData);
 }
 
 nsRefPtr<MediaDataDecoder::InitPromise>
@@ -75,29 +74,21 @@ FFmpegH264Decoder<LIBAV_VER>::GetPts(con
 FFmpegH264Decoder<LIBAV_VER>::DecodeResult
 FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 
   uint8_t* inputData = const_cast<uint8_t*>(aSample->Data());
   size_t inputSize = aSample->Size();
 
-  if (inputSize && (mCodecID == AV_CODEC_ID_VP8
+  if (inputSize && mCodecParser && (mCodecID == AV_CODEC_ID_VP8
 #if LIBAVCODEC_VERSION_MAJOR >= 55
       || mCodecID == AV_CODEC_ID_VP9
 #endif
       )) {
-    if (!mCodecParser) {
-      mCodecParser = av_parser_init(mCodecID);
-      if (!mCodecParser) {
-        mCallback->Error();
-        return DecodeResult::DECODE_ERROR;
-      }
-      mCodecParser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
-    }
     bool gotFrame = false;
     while (inputSize) {
       uint8_t* data;
       int size;
       int len = av_parser_parse2(mCodecParser, mCodecContext, &data, &size,
                                  inputData, inputSize,
                                  aSample->mTime, aSample->mTimecode,
                                  aSample->mOffset);
@@ -356,20 +347,16 @@ FFmpegH264Decoder<LIBAV_VER>::ProcessDra
   while (DoDecodeFrame(empty) == DecodeResult::DECODE_FRAME) {
   }
   mCallback->DrainComplete();
 }
 
 FFmpegH264Decoder<LIBAV_VER>::~FFmpegH264Decoder()
 {
   MOZ_COUNT_DTOR(FFmpegH264Decoder);
-  if (mCodecParser) {
-    av_parser_close(mCodecParser);
-    mCodecParser = nullptr;
-  }
 }
 
 AVCodecID
 FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType)
 {
   if (aMimeType.EqualsLiteral("video/avc") || aMimeType.EqualsLiteral("video/mp4")) {
     return AV_CODEC_ID_H264;
   }
--- a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h
@@ -61,14 +61,13 @@ private:
   static void ReleaseBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
   int64_t GetPts(const AVPacket& packet);
 
   nsRefPtr<ImageContainer> mImageContainer;
   uint32_t mPictureWidth;
   uint32_t mPictureHeight;
   uint32_t mDisplayWidth;
   uint32_t mDisplayHeight;
-  AVCodecParserContext* mCodecParser;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegH264Decoder_h__
--- a/dom/media/systemservices/CamerasParent.cpp
+++ b/dom/media/systemservices/CamerasParent.cpp
@@ -156,30 +156,31 @@ CamerasParent::DeliverFrameOverIPC(Captu
   // and copy into it. That is an extra copy, but we expect this to be
   // the exceptional case, because we just assured the next call *will* have a
   // buffer of the right size.
   if (altbuffer != nullptr) {
     // Get a shared memory buffer from the pool, at least size big
     ShmemBuffer shMemBuff = mShmemPool.Get(this, size);
 
     if (!shMemBuff.Valid()) {
-      LOG(("Video shmem is not writeable in DeliverFrame"));
+      LOG(("No usable Video shmem in DeliverFrame (out of buffers?)"));
       // We can skip this frame if we run out of buffers, it's not a real error.
       return 0;
     }
 
     // get() and Size() check for proper alignment of the segment
     memcpy(shMemBuff.GetBytes(), altbuffer, size);
 
     if (!SendDeliverFrame(cap_engine, cap_id,
                           shMemBuff.Get(), size,
                           time_stamp, ntp_time, render_time)) {
       return -1;
     }
   } else {
+    MOZ_ASSERT(buffer.Valid());
     // ShmemBuffer was available, we're all good. A single copy happened
     // in the original webrtc callback.
     if (!SendDeliverFrame(cap_engine, cap_id,
                           buffer.Get(), size,
                           time_stamp, ntp_time, render_time)) {
       return -1;
     }
   }
@@ -200,17 +201,17 @@ CallbackHelper::DeliverFrame(unsigned ch
                              int64_t ntp_time,
                              int64_t render_time,
                              void *handle)
 {
   // Get a shared memory buffer to copy the frame data into
   ShmemBuffer shMemBuffer = mParent->GetBuffer(size);
   if (!shMemBuffer.Valid()) {
     // Either we ran out of buffers or they're not the right size yet
-    LOG(("Video shmem is not available in DeliverFrame"));
+    LOG(("Correctly sized Video shmem not available in DeliverFrame"));
     // We will do the copy into a(n extra) temporary buffer inside
     // the DeliverFrameRunnable constructor.
   } else {
     // Shared memory buffers of the right size are available, do the copy here.
     memcpy(shMemBuffer.GetBytes(), buffer, size);
     // Mark the original buffer as cleared.
     buffer = nullptr;
   }
--- a/dom/media/systemservices/ShmemPool.cpp
+++ b/dom/media/systemservices/ShmemPool.cpp
@@ -35,22 +35,24 @@ mozilla::ShmemBuffer ShmemPool::GetIfAva
   if (mPoolFree == 0) {
     // This isn't initialized, so will be understood as an error.
     return ShmemBuffer();
   }
 
   ShmemBuffer& res = mShmemPool[mPoolFree - 1];
 
   if (!res.mInitialized) {
+    LOG(("No free preallocated Shmem"));
     return ShmemBuffer();
   }
 
   MOZ_ASSERT(res.mShmem.IsWritable(), "Pool in Shmem is not writable?");
 
   if (res.mShmem.Size<char>() < aSize) {
+    LOG(("Free Shmem but not of the right size"));
     return ShmemBuffer();
   }
 
   mPoolFree--;
 #ifdef DEBUG
   size_t poolUse = mShmemPool.Length() - mPoolFree;
   if (poolUse > mMaxPoolUse) {
     mMaxPoolUse = poolUse;
@@ -66,48 +68,69 @@ mozilla::ShmemBuffer ShmemPool::Get(T* a
   MutexAutoLock lock(mMutex);
 
   // Pool is empty, don't block caller.
   if (mPoolFree == 0) {
     // This isn't initialized, so will be understood as an error.
     return ShmemBuffer();
   }
 
-  ShmemBuffer res = Move(mShmemPool[mPoolFree - 1]);
+  ShmemBuffer& res = mShmemPool[mPoolFree - 1];
 
   if (!res.mInitialized) {
-    LOG(("Initiaizing new Shmem in pool"));
-    aInstance->AllocShmem(aSize, SharedMemory::TYPE_BASIC, &res.mShmem);
+    LOG(("Initializing new Shmem in pool"));
+    if (!aInstance->AllocShmem(aSize, SharedMemory::TYPE_BASIC, &res.mShmem)) {
+      LOG(("Failure allocating new Shmem buffer"));
+      return ShmemBuffer();
+    }
     res.mInitialized = true;
   }
 
-  MOZ_ASSERT(res.mShmem.IsWritable(), "Pool in Shmem is not writable?");
+  MOZ_ASSERT(res.mShmem.IsWritable(), "Shmem in Pool is not writable?");
 
   // Prepare buffer, increase size if needed (we never shrink as we don't
   // maintain seperate sized pools and we don't want to keep reallocating)
   if (res.mShmem.Size<char>() < aSize) {
     LOG(("Size change/increase in Shmem Pool"));
     aInstance->DeallocShmem(res.mShmem);
+    res.mInitialized = false;
     // this may fail; always check return value
     if (!aInstance->AllocShmem(aSize, SharedMemory::TYPE_BASIC, &res.mShmem)) {
-      LOG(("Failure allocating new size Shmem buffer"));
+      LOG(("Failure allocating resized Shmem buffer"));
       return ShmemBuffer();
+    } else {
+      res.mInitialized = true;
     }
   }
 
+  MOZ_ASSERT(res.mShmem.IsWritable(), "Shmem in Pool is not writable post resize?");
+
   mPoolFree--;
-  return res;
+#ifdef DEBUG
+  size_t poolUse = mShmemPool.Length() - mPoolFree;
+  if (poolUse > mMaxPoolUse) {
+    mMaxPoolUse = poolUse;
+    LOG(("Maximum ShmemPool use increased: %d buffers", mMaxPoolUse));
+  }
+#endif
+  return Move(res);
 }
 
 void ShmemPool::Put(ShmemBuffer&& aShmem)
 {
   MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mPoolFree < mShmemPool.Length());
   mShmemPool[mPoolFree] = Move(aShmem);
   mPoolFree++;
+#ifdef DEBUG
+  size_t poolUse = mShmemPool.Length() - mPoolFree;
+  if (poolUse > 0) {
+    LOG(("ShmemPool usage reduced to %d buffers", poolUse));
+  }
+#endif
 }
 
 template <class T>
 void ShmemPool::Cleanup(T* aInstance)
 {
   MutexAutoLock lock(mMutex);
   for (size_t i = 0; i < mShmemPool.Length(); i++) {
     if (mShmemPool[i].mInitialized) {
--- a/dom/media/test/eme.js
+++ b/dom/media/test/eme.js
@@ -394,17 +394,16 @@ function SetupEME(test, token, params)
     }
   });
   return v;
 }
 
 function SetupEMEPref(callback) {
   var prefs = [
     [ "media.mediasource.enabled", true ],
-    [ "media.fragmented-mp4.exposed", true ],
     [ "media.eme.apiVisible", true ],
   ];
 
   if (/Linux/.test(manifestNavigator().userAgent)) {
     prefs.push([ "media.fragmented-mp4.ffmpeg.enabled", true ]);
   } else if (SpecialPowers.Services.appinfo.name == "B2G" ||
              !manifestVideo().canPlayType("video/mp4")) {
    // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
--- a/dom/media/test/test_can_play_type_mpeg.html
+++ b/dom/media/test/test_can_play_type_mpeg.html
@@ -126,18 +126,17 @@ function IsLinuxGStreamer() {
 }
 
 function IsJellyBeanOrLater() {
   return androidVersion >= 16;
 }
 
 // Check whether we should expect the new MP4Reader-based support to work.
 function IsMP4ReaderAvailable() {
-  var prefs = getPref("media.fragmented-mp4.enabled") &&
-              getPref("media.fragmented-mp4.exposed");
+  var prefs = getPref("media.fragmented-mp4.enabled");
   return prefs && (IsWindowsVistaOrLater() || IsMacOSSnowLeopardOrLater() || IsJellyBeanOrLater());
 }
 
 var haveMp4 = IsWindowsVistaOrLater() ||
               getPref("media.omx.enabled") ||
               getPref("media.gstreamer.enabled") ||
               IsMP4ReaderAvailable();
 // TODO:  Add "getPref("media.plugins.enabled")" once MP4 works on Gingerbread.
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -61,28 +61,30 @@ public:
     }
   }
 
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
 
-  virtual void SetTimelineParameter(uint32_t aIndex,
-                                    const dom::AudioParamTimeline& aValue,
-                                    TrackRate aSampleRate) override
+  virtual void RecvTimelineEvent(uint32_t aIndex,
+                                 dom::AudioTimelineEvent& aEvent) override
   {
+    MOZ_ASSERT(mSource && mDestination);
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case AudioBufferSourceNode::PLAYBACKRATE:
-      mPlaybackRateTimeline = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mPlaybackRateTimeline, mSource, mDestination);
+      mPlaybackRateTimeline.InsertEvent<int64_t>(aEvent);
       break;
     case AudioBufferSourceNode::DETUNE:
-      mDetuneTimeline = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mDetuneTimeline, mSource, mDestination);
+      mDetuneTimeline.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter");
     }
   }
   virtual void SetStreamTimeParameter(uint32_t aIndex, StreamTime aParam) override
   {
     switch (aIndex) {
@@ -780,33 +782,35 @@ AudioBufferSourceNode::NotifyMainThreadS
   NS_DispatchToMainThread(new EndedEventDispatcher(this));
 
   // Drop the playing reference
   // Warning: The below line might delete this.
   MarkInactive();
 }
 
 void
-AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode)
+AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode,
+                                                const AudioTimelineEvent& aEvent)
 {
   AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
   if (!This->mStream) {
     return;
   }
-  SendTimelineParameterToStream(This, PLAYBACKRATE, *This->mPlaybackRate);
+  SendTimelineEventToStream(This, PLAYBACKRATE, aEvent);
 }
 
 void
-AudioBufferSourceNode::SendDetuneToStream(AudioNode* aNode)
+AudioBufferSourceNode::SendDetuneToStream(AudioNode* aNode,
+                                          const AudioTimelineEvent& aEvent)
 {
   AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
   if (!This->mStream) {
     return;
   }
-  SendTimelineParameterToStream(This, DETUNE, *This->mDetune);
+  SendTimelineEventToStream(This, DETUNE, aEvent);
 }
 
 void
 AudioBufferSourceNode::SendDopplerShiftToStream(double aDopplerShift)
 {
   MOZ_ASSERT(mStream, "Should have disconnected panner if no stream");
   SendDoubleParameterToStream(DOPPLERSHIFT, aDopplerShift);
 }
--- a/dom/media/webaudio/AudioBufferSourceNode.h
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -124,18 +124,20 @@ private:
     PLAYBACKRATE,
     DETUNE,
     DOPPLERSHIFT
   };
 
   void SendLoopParametersToStream();
   void SendBufferParameterToStream(JSContext* aCx);
   void SendOffsetAndDurationParametersToStream(AudioNodeStream* aStream);
-  static void SendPlaybackRateToStream(AudioNode* aNode);
-  static void SendDetuneToStream(AudioNode* aNode);
+  static void SendPlaybackRateToStream(AudioNode* aNode,
+                                       const AudioTimelineEvent& aEvent);
+  static void SendDetuneToStream(AudioNode* aNode,
+                                 const AudioTimelineEvent& aEvent);
 
 private:
   double mLoopStart;
   double mLoopEnd;
   double mOffset;
   double mDuration;
   nsRefPtr<AudioBuffer> mBuffer;
   nsRefPtr<AudioParam> mPlaybackRate;
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -664,18 +664,18 @@ AudioContext::DestinationStream() const
   }
   return nullptr;
 }
 
 double
 AudioContext::CurrentTime() const
 {
   MediaStream* stream = Destination()->Stream();
-  return StreamTimeToDOMTime(stream->
-                             StreamTimeToSeconds(stream->GetCurrentTime()));
+  return stream->StreamTimeToSeconds(stream->GetCurrentTime() +
+                                     Destination()->ExtraCurrentTime());
 }
 
 void
 AudioContext::Shutdown()
 {
   mIsShutDown = true;
 
   if (!mIsOffline) {
@@ -1089,22 +1089,16 @@ NS_IMETHODIMP
 AudioContext::CollectReports(nsIHandleReportCallback* aHandleReport,
                              nsISupports* aData, bool aAnonymize)
 {
   int64_t amount = SizeOfIncludingThis(MallocSizeOf);
   return MOZ_COLLECT_REPORT("explicit/webaudio/audiocontext", KIND_HEAP, UNITS_BYTES,
                             amount, "Memory used by AudioContext objects (Web Audio).");
 }
 
-double
-AudioContext::ExtraCurrentTime() const
-{
-  return mDestination->ExtraCurrentTime();
-}
-
 BasicWaveFormCache*
 AudioContext::GetBasicWaveFormCache()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mBasicWaveFormCache) {
     mBasicWaveFormCache = new BasicWaveFormCache(SampleRate());
   }
   return mBasicWaveFormCache;
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -106,17 +106,17 @@ public:
 
 private:
   nsRefPtr<AudioContext> mAudioContext;
   void* mPromise;
   nsRefPtr<AudioNodeStream> mAudioNodeStream;
   AudioContextState mNewState;
 };
 
-enum AudioContextOperation { Suspend, Resume, Close };
+enum class AudioContextOperation { Suspend, Resume, Close };
 
 class AudioContext final : public DOMEventTargetHelper,
                            public nsIMemoryReporter
 {
   AudioContext(nsPIDOMWindow* aParentWindow,
                bool aIsOffline,
                AudioChannel aChannel,
                uint32_t aNumberOfChannels = 0,
@@ -301,41 +301,32 @@ public:
 
   AudioChannel TestAudioChannelInAudioNodeStream();
 
   void RegisterNode(AudioNode* aNode);
   void UnregisterNode(AudioNode* aNode);
 
   double DOMTimeToStreamTime(double aTime) const
   {
-    return aTime - ExtraCurrentTime();
+    return aTime;
   }
 
   double StreamTimeToDOMTime(double aTime) const
   {
-    return aTime + ExtraCurrentTime();
+    return aTime;
   }
 
   void OnStateChanged(void* aPromise, AudioContextState aNewState);
 
   BasicWaveFormCache* GetBasicWaveFormCache();
 
   IMPL_EVENT_HANDLER(mozinterruptbegin)
   IMPL_EVENT_HANDLER(mozinterruptend)
 
 private:
-  /**
-   * Returns the amount of extra time added to the current time of the
-   * AudioDestinationNode's MediaStream to get this AudioContext's currentTime.
-   * Must be subtracted from all DOM API parameter times that are on the same
-   * timeline as AudioContext's currentTime to get times we can pass to the
-   * MediaStreamGraph.
-   */
-  double ExtraCurrentTime() const;
-
   void RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob);
   void ShutdownDecoder();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                             nsISupports* aData, bool aAnonymize) override;
 
   friend struct ::mozilla::WebAudioDecodeJob;
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -330,17 +330,16 @@ AudioDestinationNode::AudioDestinationNo
                                            uint32_t aNumberOfChannels,
                                            uint32_t aLength, float aSampleRate)
   : AudioNode(aContext, aIsOffline ? aNumberOfChannels : 2,
               ChannelCountMode::Explicit, ChannelInterpretation::Speakers)
   , mFramesToProduce(aLength)
   , mAudioChannel(AudioChannel::Normal)
   , mIsOffline(aIsOffline)
   , mAudioChannelAgentPlaying(false)
-  , mExtraCurrentTime(0)
   , mExtraCurrentTimeSinceLastStartedBlocking(0)
   , mExtraCurrentTimeUpdatedSinceLastStableState(false)
   , mCaptured(false)
 {
   MediaStreamGraph* graph = aIsOffline ?
                             MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
                             MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, aChannel);
   AudioNodeEngine* engine = aIsOffline ?
@@ -660,27 +659,30 @@ AudioDestinationNode::ScheduleStableStat
 {
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(this, &AudioDestinationNode::NotifyStableState);
   // Dispatch will fail if this is called on AudioNode destruction during
   // shutdown, in which case failure can be ignored.
   nsContentUtils::RunInStableState(event.forget());
 }
 
-double
+StreamTime
 AudioDestinationNode::ExtraCurrentTime()
 {
   if (!mStartedBlockingDueToBeingOnlyNode.IsNull() &&
       !mExtraCurrentTimeUpdatedSinceLastStableState) {
     mExtraCurrentTimeUpdatedSinceLastStableState = true;
-    mExtraCurrentTimeSinceLastStartedBlocking =
+    // Round to nearest processing block.
+    double seconds =
       (TimeStamp::Now() - mStartedBlockingDueToBeingOnlyNode).ToSeconds();
+    mExtraCurrentTimeSinceLastStartedBlocking = WEBAUDIO_BLOCK_SIZE *
+      StreamTime(seconds * Context()->SampleRate() / WEBAUDIO_BLOCK_SIZE + 0.5);
     ScheduleStableStateNotification();
   }
-  return mExtraCurrentTime + mExtraCurrentTimeSinceLastStartedBlocking;
+  return mExtraCurrentTimeSinceLastStartedBlocking;
 }
 
 void
 AudioDestinationNode::SetIsOnlyNodeForContext(bool aIsOnlyNode)
 {
   if (!mStartedBlockingDueToBeingOnlyNode.IsNull() == aIsOnlyNode) {
     // Nothing changed.
     return;
@@ -703,19 +705,18 @@ AudioDestinationNode::SetIsOnlyNodeForCo
     mStream->Suspend();
     mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now();
     // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state.
     mExtraCurrentTimeUpdatedSinceLastStableState = true;
     ScheduleStableStateNotification();
   } else {
     // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary
     ExtraCurrentTime();
-    mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking;
+    mStream->AdvanceAndResume(mExtraCurrentTimeSinceLastStartedBlocking);
     mExtraCurrentTimeSinceLastStartedBlocking = 0;
-    mStream->Resume();
     mStartedBlockingDueToBeingOnlyNode = TimeStamp();
   }
 }
 
 void
 AudioDestinationNode::InputMuted(bool aMuted)
 {
   MOZ_ASSERT(Context() && !Context()->IsOffline());
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -63,17 +63,17 @@ public:
   AudioChannel MozAudioChannelType() const;
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
   virtual void NotifyMainThreadStreamFinished() override;
   void FireOfflineCompletionEvent();
 
   // An amount that should be added to the MediaStream's current time to
   // get the AudioContext.currentTime.
-  double ExtraCurrentTime();
+  StreamTime ExtraCurrentTime();
 
   // When aIsOnlyNode is true, this is the only node for the AudioContext.
   void SetIsOnlyNodeForContext(bool aIsOnlyNode);
 
   void CreateAudioChannelAgent();
   void DestroyAudioChannelAgent();
 
   virtual const char* NodeType() const override
@@ -107,18 +107,17 @@ private:
   nsRefPtr<Promise> mOfflineRenderingPromise;
 
   // Audio Channel Type.
   AudioChannel mAudioChannel;
   bool mIsOffline;
   bool mAudioChannelAgentPlaying;
 
   TimeStamp mStartedBlockingDueToBeingOnlyNode;
-  double mExtraCurrentTime;
-  double mExtraCurrentTimeSinceLastStartedBlocking;
+  StreamTime mExtraCurrentTimeSinceLastStartedBlocking;
   bool mExtraCurrentTimeUpdatedSinceLastStableState;
   bool mCaptured;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/media/webaudio/AudioEventTimeline.h
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -7,92 +7,86 @@
 #ifndef AudioEventTimeline_h_
 #define AudioEventTimeline_h_
 
 #include <algorithm>
 #include "mozilla/Assertions.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/PodOperations.h"
 
+#include "MainThreadUtils.h"
 #include "nsTArray.h"
 #include "math.h"
 #include "WebAudioUtils.h"
 
 namespace mozilla {
 
+class MediaStream;
+
 namespace dom {
 
-// This is an internal helper class and should not be used outside of this header.
 struct AudioTimelineEvent final
 {
   enum Type : uint32_t
   {
     SetValue,
+    SetValueAtTime,
     LinearRamp,
     ExponentialRamp,
     SetTarget,
-    SetValueCurve
+    SetValueCurve,
+    Stream,
+    Cancel
   };
 
   AudioTimelineEvent(Type aType, double aTime, float aValue, double aTimeConstant = 0.0,
-                     float aDuration = 0.0, const float* aCurve = nullptr, uint32_t aCurveLength = 0)
+                     float aDuration = 0.0, const float* aCurve = nullptr,
+                     uint32_t aCurveLength = 0)
     : mType(aType)
     , mTimeConstant(aTimeConstant)
     , mDuration(aDuration)
 #ifdef DEBUG
     , mTimeIsInTicks(false)
 #endif
   {
     mTime = aTime;
     if (aType == AudioTimelineEvent::SetValueCurve) {
       SetCurveParams(aCurve, aCurveLength);
     } else {
       mValue = aValue;
     }
   }
 
+  explicit AudioTimelineEvent(MediaStream* aStream)
+    : mType(Stream)
+    , mStream(aStream)
+#ifdef DEBUG
+    , mTimeIsInTicks(false)
+#endif
+  {
+  }
+
   AudioTimelineEvent(const AudioTimelineEvent& rhs)
   {
     PodCopy(this, &rhs, 1);
+
     if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
       SetCurveParams(rhs.mCurve, rhs.mCurveLength);
+    } else if (rhs.mType == AudioTimelineEvent::Stream) {
+      new (&mStream) decltype(mStream)(rhs.mStream);
     }
   }
 
   ~AudioTimelineEvent()
   {
     if (mType == AudioTimelineEvent::SetValueCurve) {
       delete[] mCurve;
     }
   }
 
-  bool IsValid() const
-  {
-    if (mType == AudioTimelineEvent::SetValueCurve) {
-      if (!mCurve || !mCurveLength) {
-        return false;
-      }
-      for (uint32_t i = 0; i < mCurveLength; ++i) {
-        if (!IsValid(mCurve[i])) {
-          return false;
-        }
-      }
-    }
-
-    if (mType == AudioTimelineEvent::SetTarget &&
-        WebAudioUtils::FuzzyEqual(mTimeConstant, 0.0)) {
-      return false;
-    }
-
-    return IsValid(mTime) &&
-           IsValid(mValue) &&
-           IsValid(mTimeConstant) &&
-           IsValid(mDuration);
-  }
-
   template <class TimeType>
   TimeType Time() const;
 
   void SetTimeInTicks(int64_t aTimeInTicks)
   {
     mTimeInTicks = aTimeInTicks;
 #ifdef DEBUG
     mTimeIsInTicks = true;
@@ -109,54 +103,54 @@ struct AudioTimelineEvent final
     }
   }
 
   Type mType;
   union {
     float mValue;
     uint32_t mCurveLength;
   };
+  // mCurve contains a buffer of SetValueCurve samples.  We sample the
+  // values in the buffer depending on how far along we are in time.
+  // If we're at time T and the event has started as time T0 and has a
+  // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
+  // if T<T0+D, and just take the last sample in the buffer otherwise.
+  float* mCurve;
+  nsRefPtr<MediaStream> mStream;
+  double mTimeConstant;
+  double mDuration;
+#ifdef DEBUG
+  bool mTimeIsInTicks;
+#endif
+
+private:
+  // This member is accessed using the `Time` method, for safety.
+  //
   // The time for an event can either be in absolute value or in ticks.
   // Initially the time of the event is always in absolute value.
   // In order to convert it to ticks, call SetTimeInTicks.  Once this
   // method has been called for an event, the time cannot be converted
   // back to absolute value.
   union {
     double mTime;
     int64_t mTimeInTicks;
   };
-  // mCurve contains a buffer of SetValueCurve samples.  We sample the
-  // values in the buffer depending on how far along we are in time.
-  // If we're at time T and the event has started as time T0 and has a
-  // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
-  // if T<T0+D, and just take the last sample in the buffer otherwise.
-  float* mCurve;
-  double mTimeConstant;
-  double mDuration;
-#ifdef DEBUG
-  bool mTimeIsInTicks;
-#endif
-
-private:
-  static bool IsValid(double value)
-  {
-    return mozilla::IsFinite(value);
-  }
 };
 
 template <>
 inline double AudioTimelineEvent::Time<double>() const
 {
   MOZ_ASSERT(!mTimeIsInTicks);
   return mTime;
 }
 
 template <>
 inline int64_t AudioTimelineEvent::Time<int64_t>() const
 {
+  MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(mTimeIsInTicks);
   return mTimeInTicks;
 }
 
 /**
  * This class will be instantiated with different template arguments for testing and
  * production code.
  *
@@ -166,17 +160,129 @@ inline int64_t AudioTimelineEvent::Time<
 template <class ErrorResult>
 class AudioEventTimeline
 {
 public:
   explicit AudioEventTimeline(float aDefaultValue)
     : mValue(aDefaultValue),
       mComputedValue(aDefaultValue),
       mLastComputedValue(aDefaultValue)
+  { }
+
+  bool ValidateEvent(AudioTimelineEvent& aEvent,
+                     ErrorResult& aRv)
   {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Validate the event itself
+    if (!WebAudioUtils::IsTimeValid(aEvent.template Time<double>()) ||
+        !WebAudioUtils::IsTimeValid(aEvent.mTimeConstant)) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return false;
+    }
+
+    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
+      if (!aEvent.mCurve || !aEvent.mCurveLength) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return false;
+      }
+      for (uint32_t i = 0; i < aEvent.mCurveLength; ++i) {
+        if (!IsValid(aEvent.mCurve[i])) {
+          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+          return false;
+        }
+      }
+    }
+
+    if (aEvent.mType == AudioTimelineEvent::SetTarget &&
+        WebAudioUtils::FuzzyEqual(aEvent.mTimeConstant, 0.0)) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return false;
+    }
+
+    bool timeAndValueValid = IsValid(aEvent.mValue) &&
+                             IsValid(aEvent.mDuration);
+    if (!timeAndValueValid) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return false;
+    }
+
+    // Make sure that non-curve events don't fall within the duration of a
+    // curve event.
+    for (unsigned i = 0; i < mEvents.Length(); ++i) {
+      if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
+          mEvents[i].template Time<double>() <= aEvent.template Time<double>() &&
+          (mEvents[i].template Time<double>() + mEvents[i].mDuration) >= aEvent.template Time<double>()) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return false;
+      }
+    }
+
+    // Make sure that curve events don't fall in a range which includes other
+    // events.
+    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
+      for (unsigned i = 0; i < mEvents.Length(); ++i) {
+        if (mEvents[i].template Time<double>() > aEvent.template Time<double>() &&
+            mEvents[i].template Time<double>() < (aEvent.template Time<double>() + aEvent.mDuration)) {
+          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+          return false;
+        }
+      }
+    }
+
+    // Make sure that invalid values are not used for exponential curves
+    if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
+      if (aEvent.mValue <= 0.f) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return false;
+      }
+      const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.template Time<double>());
+      if (previousEvent) {
+        if (previousEvent->mValue <= 0.f) {
+          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+          return false;
+        }
+      } else {
+        if (mValue <= 0.f) {
+          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  template<typename TimeType>
+  void InsertEvent(const AudioTimelineEvent& aEvent)
+  {
+    for (unsigned i = 0; i < mEvents.Length(); ++i) {
+      if (aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>()) {
+        if (aEvent.mType == mEvents[i].mType) {
+          // If times and types are equal, replace the event
+          mEvents.ReplaceElementAt(i, aEvent);
+        } else {
+          // Otherwise, place the element after the last event of another type
+          do {
+            ++i;
+          } while (i < mEvents.Length() &&
+                   aEvent.mType != mEvents[i].mType &&
+                   aEvent.template Time<TimeType>() == mEvents[i].template Time<TimeType>());
+          mEvents.InsertElementAt(i, aEvent);
+        }
+        return;
+      }
+      // Otherwise, place the event right after the latest existing event
+      if (aEvent.template Time<TimeType>() < mEvents[i].template Time<TimeType>()) {
+        mEvents.InsertElementAt(i, aEvent);
+        return;
+      }
+    }
+
+    // If we couldn't find a place for the event, just append it to the list
+    mEvents.AppendElement(aEvent);
   }
 
   bool HasSimpleValue() const
   {
     return mEvents.IsEmpty();
   }
 
   float GetValue() const
@@ -197,48 +303,68 @@ public:
     // Silently don't change anything if there are any events
     if (mEvents.IsEmpty()) {
       mLastComputedValue = mComputedValue = mValue = aValue;
     }
   }
 
   void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
   {
-    InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetValue, aStartTime, aValue), aRv);
+    AudioTimelineEvent event(AudioTimelineEvent::SetValueAtTime, aStartTime, aValue);
+
+    if (ValidateEvent(event, aRv)) {
+      InsertEvent<double>(event);
+    }
   }
 
   void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
-    InsertEvent(AudioTimelineEvent(AudioTimelineEvent::LinearRamp, aEndTime, aValue), aRv);
+    AudioTimelineEvent event(AudioTimelineEvent::LinearRamp, aEndTime, aValue);
+
+    if (ValidateEvent(event, aRv)) {
+      InsertEvent<double>(event);
+    }
   }
 
   void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
-    InsertEvent(AudioTimelineEvent(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue), aRv);
+    AudioTimelineEvent event(AudioTimelineEvent::ExponentialRamp, aEndTime, aValue);
+
+    if (ValidateEvent(event, aRv)) {
+      InsertEvent<double>(event);
+    }
   }
 
   void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
   {
-    InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant), aRv);
+    AudioTimelineEvent event(AudioTimelineEvent::SetTarget, aStartTime, aTarget, aTimeConstant);
+
+    if (ValidateEvent(event, aRv)) {
+      InsertEvent<double>(event);
+    }
   }
 
   void SetValueCurveAtTime(const float* aValues, uint32_t aValuesLength, double aStartTime, double aDuration, ErrorResult& aRv)
   {
-    InsertEvent(AudioTimelineEvent(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength), aRv);
+    AudioTimelineEvent event(AudioTimelineEvent::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues, aValuesLength);
+    if (ValidateEvent(event, aRv)) {
+      InsertEvent<double>(event);
+    }
   }
 
-  void CancelScheduledValues(double aStartTime)
+  template<typename TimeType>
+  void CancelScheduledValues(TimeType aStartTime)
   {
     for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (mEvents[i].mTime >= aStartTime) {
+      if (mEvents[i].template Time<TimeType>() >= aStartTime) {
 #ifdef DEBUG
         // Sanity check: the array should be sorted, so all of the following
         // events should have a time greater than aStartTime too.
         for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
-          MOZ_ASSERT(mEvents[j].mTime >= aStartTime);
+          MOZ_ASSERT(mEvents[j].template Time<TimeType>() >= aStartTime);
         }
 #endif
         mEvents.TruncateLength(i);
         break;
       }
     }
   }
 
@@ -293,17 +419,17 @@ public:
       mEvents.RemoveElementAt(0);
     }
 
     for (size_t bufferIndex = 0; bufferIndex < aSize; ++bufferIndex, ++aTime) {
       for (; !bailOut && lastEventId < mEvents.Length(); ++lastEventId) {
 
 #ifdef DEBUG
         const AudioTimelineEvent* current = &mEvents[lastEventId];
-        MOZ_ASSERT(current->mType == AudioTimelineEvent::SetValue ||
+        MOZ_ASSERT(current->mType == AudioTimelineEvent::SetValueAtTime ||
                    current->mType == AudioTimelineEvent::SetTarget ||
                    current->mType == AudioTimelineEvent::LinearRamp ||
                    current->mType == AudioTimelineEvent::ExponentialRamp ||
                    current->mType == AudioTimelineEvent::SetValueCurve);
 #endif
 
         if (TimesEqual(aTime, mEvents[lastEventId].template Time<TimeType>())) {
           mLastComputedValue = mComputedValue;
@@ -388,27 +514,16 @@ public:
     }
     double ratio = std::max((t - startTime) / duration, 0.0);
     if (ratio >= 1.0) {
       return aCurve[aCurveLength - 1];
     }
     return aCurve[uint32_t(aCurveLength * ratio)];
   }
 
-  void ConvertEventTimesToTicks(int64_t (*aConvertor)(double aTime, void* aClosure), void* aClosure,
-                                int32_t aSampleRate)
-  {
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      mEvents[i].SetTimeInTicks(aConvertor(mEvents[i].template Time<double>(), aClosure));
-      mEvents[i].mTimeConstant *= aSampleRate;
-      mEvents[i].mDuration *= aSampleRate;
-    }
-  }
-
-private:
   template<class TimeType>
   float GetValuesAtTimeHelperInternal(TimeType aTime,
                                       const AudioTimelineEvent* aPrevious,
                                       const AudioTimelineEvent* aNext)
   {
     // If the requested time is before all of the existing events
     if (!aPrevious) {
        return mValue;
@@ -428,27 +543,31 @@ private:
       return ExtractValueFromCurve(aPrevious->template Time<TimeType>(),
                                    aPrevious->mCurve, aPrevious->mCurveLength,
                                    aPrevious->mDuration, aTime);
     }
 
     // If the requested time is after all of the existing events
     if (!aNext) {
       switch (aPrevious->mType) {
-      case AudioTimelineEvent::SetValue:
-      case AudioTimelineEvent::LinearRamp:
-      case AudioTimelineEvent::ExponentialRamp:
-        // The value will be constant after the last event
-        return aPrevious->mValue;
-      case AudioTimelineEvent::SetValueCurve:
-        return ExtractValueFromCurve(aPrevious->template Time<TimeType>(),
-                                     aPrevious->mCurve, aPrevious->mCurveLength,
-                                     aPrevious->mDuration, aTime);
-      case AudioTimelineEvent::SetTarget:
-        MOZ_ASSERT(false, "unreached");
+        case AudioTimelineEvent::SetValueAtTime:
+        case AudioTimelineEvent::LinearRamp:
+        case AudioTimelineEvent::ExponentialRamp:
+          // The value will be constant after the last event
+          return aPrevious->mValue;
+        case AudioTimelineEvent::SetValueCurve:
+          return ExtractValueFromCurve(aPrevious->template Time<TimeType>(),
+                                       aPrevious->mCurve, aPrevious->mCurveLength,
+                                       aPrevious->mDuration, aTime);
+        case AudioTimelineEvent::SetTarget:
+          MOZ_ASSERT(false, "unreached");
+        case AudioTimelineEvent::SetValue:
+        case AudioTimelineEvent::Cancel:
+        case AudioTimelineEvent::Stream:
+          MOZ_ASSERT(false, "Should have been handled earlier.");
       }
       MOZ_ASSERT(false, "unreached");
     }
 
     // Finally, handle the case where we have both a previous and a next event
 
     // First, handle the case where our range ends up in a ramp event
     switch (aNext->mType) {
@@ -459,159 +578,94 @@ private:
                                aNext->mValue, aTime);
 
     case AudioTimelineEvent::ExponentialRamp:
       return ExponentialInterpolate(aPrevious->template Time<TimeType>(),
                                     aPrevious->mValue,
                                     aNext->template Time<TimeType>(),
                                     aNext->mValue, aTime);
 
-    case AudioTimelineEvent::SetValue:
+    case AudioTimelineEvent::SetValueAtTime:
     case AudioTimelineEvent::SetTarget:
     case AudioTimelineEvent::SetValueCurve:
       break;
+    case AudioTimelineEvent::SetValue:
+    case AudioTimelineEvent::Cancel:
+    case AudioTimelineEvent::Stream:
+      MOZ_ASSERT(false, "Should have been handled earlier.");
     }
 
     // Now handle all other cases
     switch (aPrevious->mType) {
-    case AudioTimelineEvent::SetValue:
+    case AudioTimelineEvent::SetValueAtTime:
     case AudioTimelineEvent::LinearRamp:
     case AudioTimelineEvent::ExponentialRamp:
       // If the next event type is neither linear or exponential ramp, the
       // value is constant.
       return aPrevious->mValue;
     case AudioTimelineEvent::SetValueCurve:
       return ExtractValueFromCurve(aPrevious->template Time<TimeType>(),
                                    aPrevious->mCurve, aPrevious->mCurveLength,
                                    aPrevious->mDuration, aTime);
     case AudioTimelineEvent::SetTarget:
       MOZ_ASSERT(false, "unreached");
+    case AudioTimelineEvent::SetValue:
+    case AudioTimelineEvent::Cancel:
+    case AudioTimelineEvent::Stream:
+      MOZ_ASSERT(false, "Should have been handled earlier.");
     }
 
     MOZ_ASSERT(false, "unreached");
     return 0.0f;
   }
 
   const AudioTimelineEvent* GetPreviousEvent(double aTime) const
   {
     const AudioTimelineEvent* previous = nullptr;
     const AudioTimelineEvent* next = nullptr;
 
     bool bailOut = false;
     for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) {
       switch (mEvents[i].mType) {
-      case AudioTimelineEvent::SetValue:
+      case AudioTimelineEvent::SetValueAtTime:
       case AudioTimelineEvent::SetTarget:
       case AudioTimelineEvent::LinearRamp:
       case AudioTimelineEvent::ExponentialRamp:
       case AudioTimelineEvent::SetValueCurve:
-        if (aTime == mEvents[i].mTime) {
+        if (aTime == mEvents[i].template Time<double>()) {
           // Find the last event with the same time
           do {
             ++i;
           } while (i < mEvents.Length() &&
-                   aTime == mEvents[i].mTime);
+                   aTime == mEvents[i].template Time<double>());
           return &mEvents[i - 1];
         }
         previous = next;
         next = &mEvents[i];
-        if (aTime < mEvents[i].mTime) {
+        if (aTime < mEvents[i].template Time<double>()) {
           bailOut = true;
         }
         break;
       default:
         MOZ_ASSERT(false, "unreached");
       }
     }
     // Handle the case where the time is past all of the events
     if (!bailOut) {
       previous = next;
     }
 
     return previous;
   }
-
-  void InsertEvent(const AudioTimelineEvent& aEvent, ErrorResult& aRv)
+private:
+  static bool IsValid(double value)
   {
-    if (!aEvent.IsValid()) {
-      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-      return;
-    }
-
-    // Make sure that non-curve events don't fall within the duration of a
-    // curve event.
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
-          mEvents[i].mTime <= aEvent.mTime &&
-          (mEvents[i].mTime + mEvents[i].mDuration) >= aEvent.mTime) {
-        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return;
-      }
-    }
-
-    // Make sure that curve events don't fall in a range which includes other
-    // events.
-    if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
-      for (unsigned i = 0; i < mEvents.Length(); ++i) {
-        if (mEvents[i].mTime > aEvent.mTime &&
-            mEvents[i].mTime < (aEvent.mTime + aEvent.mDuration)) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return;
-        }
-      }
-    }
-
-    // Make sure that invalid values are not used for exponential curves
-    if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
-      if (aEvent.mValue <= 0.f) {
-        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-        return;
-      }
-      const AudioTimelineEvent* previousEvent = GetPreviousEvent(aEvent.mTime);
-      if (previousEvent) {
-        if (previousEvent->mValue <= 0.f) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return;
-        }
-      } else {
-        if (mValue <= 0.f) {
-          aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-          return;
-        }
-      }
-    }
-
-    for (unsigned i = 0; i < mEvents.Length(); ++i) {
-      if (aEvent.mTime == mEvents[i].mTime) {
-        if (aEvent.mType == mEvents[i].mType) {
-          // If times and types are equal, replace the event
-          mEvents.ReplaceElementAt(i, aEvent);
-        } else {
-          // Otherwise, place the element after the last event of another type
-          do {
-            ++i;
-          } while (i < mEvents.Length() &&
-                   aEvent.mType != mEvents[i].mType &&
-                   aEvent.mTime == mEvents[i].mTime);
-          mEvents.InsertElementAt(i, aEvent);
-        }
-        return;
-      }
-      // Otherwise, place the event right after the latest existing event
-      if (aEvent.mTime < mEvents[i].mTime) {
-        mEvents.InsertElementAt(i, aEvent);
-        return;
-      }
-    }
-
-    // If we couldn't find a place for the event, just append it to the list
-    mEvents.AppendElement(aEvent);
+    return mozilla::IsFinite(value);
   }
 
-private:
   // This is a sorted array of the events in the timeline.  Queries of this
   // data structure should probably be more frequent than modifications to it,
   // and that is the reason why we're using a simple array as the data structure.
   // We can optimize this in the future if the performance of the array ends up
   // being a bottleneck.
   nsTArray<AudioTimelineEvent> mEvents;
   float mValue;
   // This is the value of this AudioParam we computed at the last call.
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -297,22 +297,22 @@ AudioNode::SendChannelMixingParametersTo
 {
   if (mStream) {
     mStream->SetChannelMixingParameters(mChannelCount, mChannelCountMode,
                                         mChannelInterpretation);
   }
 }
 
 void
-AudioNode::SendTimelineParameterToStream(AudioNode* aNode, uint32_t aIndex,
-                                         const AudioParamTimeline& aValue)
+AudioNode::SendTimelineEventToStream(AudioNode* aNode, uint32_t aIndex,
+                                     const AudioTimelineEvent& aEvent)
 {
   AudioNodeStream* ns = aNode->mStream;
   MOZ_ASSERT(ns, "How come we don't have a stream here?");
-  ns->SetTimelineParameter(aIndex, aValue);
+  ns->SendTimelineEvent(aIndex, aEvent);
 }
 
 void
 AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
--- a/dom/media/webaudio/AudioNode.h
+++ b/dom/media/webaudio/AudioNode.h
@@ -23,16 +23,17 @@ namespace mozilla {
 
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 class AudioParam;
 class AudioParamTimeline;
 struct ThreeDPoint;
+struct AudioTimelineEvent;
 
 /**
  * The DOM object representing a Web Audio AudioNode.
  *
  * Each AudioNode has a MediaStream representing the actual
  * real-time processing and output of this AudioNode.
  *
  * We track the incoming and outgoing connections to other AudioNodes.
@@ -218,18 +219,18 @@ private:
 protected:
   static void Callback(AudioNode* aNode) { /* not implemented */ }
 
   // Helpers for sending different value types to streams
   void SendDoubleParameterToStream(uint32_t aIndex, double aValue);
   void SendInt32ParameterToStream(uint32_t aIndex, int32_t aValue);
   void SendThreeDPointParameterToStream(uint32_t aIndex, const ThreeDPoint& aValue);
   void SendChannelMixingParametersToStream();
-  static void SendTimelineParameterToStream(AudioNode* aNode, uint32_t aIndex,
-                                            const AudioParamTimeline& aValue);
+  static void SendTimelineEventToStream(AudioNode* aNode, uint32_t aIndex,
+                                        const dom::AudioTimelineEvent& aEvent);
 
 private:
   nsRefPtr<AudioContext> mContext;
 
 protected:
   // Must be set in the constructor. Must not be null unless finished.
   nsRefPtr<AudioNodeStream> mStream;
 
--- a/dom/media/webaudio/AudioNodeEngine.h
+++ b/dom/media/webaudio/AudioNodeEngine.h
@@ -12,16 +12,17 @@
 #include "mozilla/Mutex.h"
 
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
 class AudioParamTimeline;
 class DelayNodeEngine;
+struct AudioTimelineEvent;
 } // namespace dom
 
 class AudioBlock;
 class AudioNodeStream;
 
 /**
  * This class holds onto a set of immutable channel buffers. The storage
  * for the buffers must be malloced, but the buffer pointers and the malloc
@@ -277,21 +278,20 @@ public:
   virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
   {
     NS_ERROR("Invalid SetDoubleParameter index");
   }
   virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
   {
     NS_ERROR("Invalid SetInt32Parameter index");
   }
-  virtual void SetTimelineParameter(uint32_t aIndex,
-                                    const dom::AudioParamTimeline& aValue,
-                                    TrackRate aSampleRate)
+  virtual void RecvTimelineEvent(uint32_t aIndex,
+                                 dom::AudioTimelineEvent& aValue)
   {
-    NS_ERROR("Invalid SetTimelineParameter index");
+    NS_ERROR("Invalid RecvTimelineEvent index");
   }
   virtual void SetThreeDPointParameter(uint32_t aIndex,
                                        const dom::ThreeDPoint& aValue)
   {
     NS_ERROR("Invalid SetThreeDPointParameter index");
   }
   virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
   {
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -198,39 +198,39 @@ AudioNodeStream::SetInt32Parameter(uint3
     int32_t mValue;
     uint32_t mIndex;
   };
 
   GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
 }
 
 void
-AudioNodeStream::SetTimelineParameter(uint32_t aIndex,
-                                      const AudioParamTimeline& aValue)
+AudioNodeStream::SendTimelineEvent(uint32_t aIndex,
+                                   const AudioTimelineEvent& aEvent)
 {
   class Message final : public ControlMessage
   {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex,
-            const AudioParamTimeline& aValue)
+            const AudioTimelineEvent& aEvent)
       : ControlMessage(aStream),
-        mValue(aValue),
+        mEvent(aEvent),
         mSampleRate(aStream->SampleRate()),
         mIndex(aIndex)
     {}
     virtual void Run() override
     {
       static_cast<AudioNodeStream*>(mStream)->Engine()->
-          SetTimelineParameter(mIndex, mValue, mSampleRate);
+          RecvTimelineEvent(mIndex, mEvent);
     }
-    AudioParamTimeline mValue;
+    AudioTimelineEvent mEvent;
     TrackRate mSampleRate;
     uint32_t mIndex;
   };
-  GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
+  GraphImpl()->AppendMessage(new Message(this, aIndex, aEvent));
 }
 
 void
 AudioNodeStream::SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aValue)
 {
   class Message final : public ControlMessage
   {
   public:
@@ -372,16 +372,41 @@ AudioNodeStream::ComputedNumberOfChannel
     return std::min(aInputChannelCount, mNumberOfInputChannels);
   default:
   case ChannelCountMode::Max:
     // Nothing to do here, just shut up the compiler warning.
     return aInputChannelCount;
   }
 }
 
+class AudioNodeStream::AdvanceAndResumeMessage final : public ControlMessage {
+public:
+  AdvanceAndResumeMessage(AudioNodeStream* aStream, StreamTime aAdvance) :
+    ControlMessage(aStream), mAdvance(aAdvance) {}
+  virtual void Run() override
+  {
+    auto ns = static_cast<AudioNodeStream*>(mStream);
+    ns->mBufferStartTime -= mAdvance;
+
+    StreamBuffer::Track* track = ns->EnsureTrack(AUDIO_TRACK);
+    track->Get<AudioSegment>()->AppendNullData(mAdvance);
+
+    ns->GraphImpl()->DecrementSuspendCount(mStream);
+  }
+private:
+  StreamTime mAdvance;
+};
+
+void
+AudioNodeStream::AdvanceAndResume(StreamTime aAdvance)
+{
+  mMainThreadCurrentTime += aAdvance;
+  GraphImpl()->AppendMessage(new AdvanceAndResumeMessage(this, aAdvance));
+}
+
 void
 AudioNodeStream::ObtainInputBlock(AudioBlock& aTmpChunk,
                                   uint32_t aPortIndex)
 {
   uint32_t inputCount = mInputs.Length();
   uint32_t outputChannelCount = 1;
   nsAutoTArray<const AudioBlock*,250> inputChunks;
   for (uint32_t i = 0; i < inputCount; ++i) {
--- a/dom/media/webaudio/AudioNodeStream.h
+++ b/dom/media/webaudio/AudioNodeStream.h
@@ -9,17 +9,17 @@
 #include "MediaStreamGraph.h"
 #include "mozilla/dom/AudioNodeBinding.h"
 #include "AudioBlock.h"
 
 namespace mozilla {
 
 namespace dom {
 struct ThreeDPoint;
-class AudioParamTimeline;
+struct AudioTimelineEvent;
 class AudioContext;
 } // namespace dom
 
 class ThreadSharedFloatArrayBufferList;
 class AudioNodeEngine;
 
 /**
  * An AudioNodeStream produces one audio track with ID AUDIO_TRACK.
@@ -80,19 +80,20 @@ public:
   /**
    * Sets a parameter that's a time relative to some stream's played time.
    * This time is converted to a time relative to this stream when it's set.
    */
   void SetStreamTimeParameter(uint32_t aIndex, AudioContext* aContext,
                               double aStreamTime);
   void SetDoubleParameter(uint32_t aIndex, double aValue);
   void SetInt32Parameter(uint32_t aIndex, int32_t aValue);
-  void SetTimelineParameter(uint32_t aIndex, const dom::AudioParamTimeline& aValue);
   void SetThreeDPointParameter(uint32_t aIndex, const dom::ThreeDPoint& aValue);
   void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList>&& aBuffer);
+  // This sends a single event to the timeline on the MSG thread side.
+  void SendTimelineEvent(uint32_t aIndex, const dom::AudioTimelineEvent& aEvent);
   // This consumes the contents of aData.  aData will be emptied after this returns.
   void SetRawArrayData(nsTArray<float>& aData);
   void SetChannelMixingParameters(uint32_t aNumberOfChannels,
                                   ChannelCountMode aChannelCountMoe,
                                   ChannelInterpretation aChannelInterpretation);
   void SetPassThrough(bool aPassThrough);
   ChannelInterpretation GetChannelInterpretation()
   {
@@ -100,16 +101,24 @@ public:
   }
 
   void SetAudioParamHelperStream()
   {
     MOZ_ASSERT(!mAudioParamStream, "Can only do this once");
     mAudioParamStream = true;
   }
 
+  /*
+   * Resume stream after updating its concept of current time by aAdvance.
+   * Main thread.  Used only from AudioDestinationNode when resuming a stream
+   * suspended to save running the MediaStreamGraph when there are no other
+   * nodes in the AudioContext.
+   */
+  void AdvanceAndResume(StreamTime aAdvance);
+
   virtual AudioNodeStream* AsAudioNodeStream() override { return this; }
   virtual void AddInput(MediaInputPort* aPort) override;
   virtual void RemoveInput(MediaInputPort* aPort) override;
 
   // Graph thread only
   void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
                                   double aStreamTime);
   void SetChannelMixingParametersImpl(uint32_t aNumberOfChannels,
@@ -179,16 +188,18 @@ public:
    * inactive, or an active input is removed, or the stream finishes.  If the
    * stream is now inactive, then mInputChunks will be cleared and mLastChunks
    * will be set to null.  ProcessBlock() will not be called on the engine
    * again until SetActive() is called.
    */
   void CheckForInactive();
 
 protected:
+  class AdvanceAndResumeMessage;
+
   virtual void DestroyImpl() override;
 
   void AdvanceOutputSegment();
   void FinishOutput();
   void AccumulateInputChunk(uint32_t aInputIndex, const AudioBlock& aChunk,
                             AudioBlock* aBlock,
                             nsTArray<float>* aDownmixBuffer);
   void UpMixDownMixChunk(const AudioBlock* aChunk, uint32_t aOutputChannelCount,
--- a/dom/media/webaudio/AudioParam.cpp
+++ b/dom/media/webaudio/AudioParam.cpp
@@ -112,18 +112,20 @@ AudioParam::Stream()
   mStream = stream.forget();
 
   // Setup the AudioParam's stream as an input to the owner AudioNode's stream
   AudioNodeStream* nodeStream = mNode->GetStream();
   if (nodeStream) {
     mNodeStreamPort = nodeStream->AllocateInputPort(mStream);
   }
 
-  // Let the MSG's copy of AudioParamTimeline know about the change in the stream
-  mCallback(mNode);
+  // Send the stream to the timeline on the MSG side.
+  AudioTimelineEvent event(mStream);
+
+  mCallback(mNode, event);
 
   return mStream;
 }
 
 float
 AudioParamTimeline::AudioNodeInputValue(size_t aCounter) const
 {
   MOZ_ASSERT(mStream);
--- a/dom/media/webaudio/AudioParam.h
+++ b/dom/media/webaudio/AudioParam.h
@@ -21,17 +21,17 @@ namespace mozilla {
 namespace dom {
 
 class AudioParam final : public nsWrapperCache,
                          public AudioParamTimeline
 {
   virtual ~AudioParam();
 
 public:
-  typedef void (*CallbackType)(AudioNode*);
+  typedef void (*CallbackType)(AudioNode* aNode, const AudioTimelineEvent&);
 
   AudioParam(AudioNode* aNode,
              CallbackType aCallback,
              float aDefaultValue,
              const char* aName);
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
   NS_IMETHOD_(MozExternalRefCountType) Release(void);
@@ -53,78 +53,96 @@ public:
   // object.
   void SetValueCurveAtTime(const Float32Array& aValues, double aStartTime, double aDuration, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aStartTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
     aValues.ComputeLengthAndData();
-    AudioParamTimeline::SetValueCurveAtTime(aValues.Data(), aValues.Length(),
-                                            DOMTimeToStreamTime(aStartTime), aDuration, aRv);
-    mCallback(mNode);
+
+    EventInsertionHelper(aRv, AudioTimelineEvent::SetValueCurve,
+                         aStartTime, 0.0f, 0.0f, aDuration, aValues.Data(),
+                         aValues.Length());
   }
 
-  // We override the rest of the mutating AudioParamTimeline methods in order to make
-  // sure that the callback is called every time that this object gets mutated.
   void SetValue(float aValue)
   {
-    // Optimize away setting the same value on an AudioParam
-    if (HasSimpleValue() &&
-        WebAudioUtils::FuzzyEqual(GetValue(), aValue)) {
+    AudioTimelineEvent event(AudioTimelineEvent::SetValue, 0.0f, aValue);
+
+    ErrorResult rv;
+    if (!ValidateEvent(event, rv)) {
+      MOZ_ASSERT(false, "This should not happen, "
+                        "setting the value should always work");
       return;
     }
+
     AudioParamTimeline::SetValue(aValue);
-    mCallback(mNode);
+
+    mCallback(mNode, event);
   }
+
   void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aStartTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
-    AudioParamTimeline::SetValueAtTime(aValue, DOMTimeToStreamTime(aStartTime), aRv);
-    mCallback(mNode);
+    EventInsertionHelper(aRv, AudioTimelineEvent::SetValueAtTime,
+                         aStartTime, aValue);
   }
+
   void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aEndTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
-    AudioParamTimeline::LinearRampToValueAtTime(aValue, DOMTimeToStreamTime(aEndTime), aRv);
-    mCallback(mNode);
+    EventInsertionHelper(aRv, AudioTimelineEvent::LinearRamp, aEndTime, aValue);
   }
+
   void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aEndTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
-    AudioParamTimeline::ExponentialRampToValueAtTime(aValue, DOMTimeToStreamTime(aEndTime), aRv);
-    mCallback(mNode);
+    EventInsertionHelper(aRv, AudioTimelineEvent::ExponentialRamp,
+                         aEndTime, aValue);
   }
-  void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
+
+  void SetTargetAtTime(float aTarget, double aStartTime,
+                       double aTimeConstant, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aStartTime) ||
         !WebAudioUtils::IsTimeValid(aTimeConstant)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
-    AudioParamTimeline::SetTargetAtTime(aTarget, DOMTimeToStreamTime(aStartTime), aTimeConstant, aRv);
-    mCallback(mNode);
+    EventInsertionHelper(aRv, AudioTimelineEvent::SetTarget,
+                         aStartTime, aTarget,
+                         aTimeConstant);
   }
+
   void CancelScheduledValues(double aStartTime, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aStartTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
-    AudioParamTimeline::CancelScheduledValues(DOMTimeToStreamTime(aStartTime));
-    mCallback(mNode);
+
+    double streamTime = DOMTimeToStreamTime(aStartTime);
+
+    // Remove some events on the main thread copy.
+    AudioEventTimeline::CancelScheduledValues(streamTime);
+
+    AudioTimelineEvent event(AudioTimelineEvent::Cancel,
+                             streamTime, 0.0f);
+
+    mCallback(mNode, event);
   }
 
   uint32_t ParentNodeId()
   {
     return mNode->Id();
   }
 
   void GetName(nsAString& aName)
@@ -183,16 +201,37 @@ public:
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 protected:
   nsCycleCollectingAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
 private:
+  void EventInsertionHelper(ErrorResult& aRv,
+                            AudioTimelineEvent::Type aType,
+                            double aTime, float aValue,
+                            double aTimeConstant = 0.0,
+                            float aDuration = 0.0,
+                            const float* aCurve = nullptr,
+                            uint32_t aCurveLength = 0)
+  {
+    AudioTimelineEvent event(aType,
+                             DOMTimeToStreamTime(aTime), aValue,
+                             aTimeConstant, aDuration, aCurve, aCurveLength);
+
+    if (!ValidateEvent(event, aRv)) {
+      return;
+    }
+
+    AudioEventTimeline::InsertEvent<double>(event);
+
+    mCallback(mNode, event);
+  }
+
   nsRefPtr<AudioNode> mNode;
   // For every InputNode, there is a corresponding entry in mOutputParams of the
   // InputNode's mInputNode.
   nsTArray<AudioNode::InputNode> mInputNodes;
   CallbackType mCallback;
   const float mDefaultValue;
   const char* mName;
   // The input port used to connect the AudioParam's stream to its node's stream
--- a/dom/media/webaudio/AudioParamTimeline.h
+++ b/dom/media/webaudio/AudioParamTimeline.h
@@ -45,16 +45,34 @@ public:
   }
 
   template<class TimeType>
   float GetValueAtTime(TimeType aTime)
   {
     return GetValueAtTime(aTime, 0);
   }
 
+  template<typename TimeType>
+  void InsertEvent(const AudioTimelineEvent& aEvent)
+  {
+    if (aEvent.mType == AudioTimelineEvent::Cancel) {
+      CancelScheduledValues(aEvent.template Time<TimeType>());
+      return;
+    }
+    if (aEvent.mType == AudioTimelineEvent::Stream) {
+      mStream = aEvent.mStream;
+      return;
+    }
+    if (aEvent.mType == AudioTimelineEvent::SetValue) {
+      AudioEventTimeline::SetValue(aEvent.mValue);
+      return;
+    }
+    AudioEventTimeline::InsertEvent<TimeType>(aEvent);
+  }
+
   // Get the value of the AudioParam at time aTime + aCounter.
   // aCounter here is an offset to aTime if we try to get the value in ticks,
   // otherwise it should always be zero.  aCounter is meant to be used when
   template<class TimeType>
   float GetValueAtTime(TimeType aTime, size_t aCounter);
 
   // Get the values of the AudioParam at time aTime + (0 to aSize).
   // aBuffer must have the correct aSize.
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -105,37 +105,37 @@ public:
   void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override
   {
     switch (aIndex) {
     case TYPE: mType = static_cast<BiquadFilterType>(aValue); break;
     default:
       NS_ERROR("Bad BiquadFilterNode Int32Parameter");
     }
   }
-  void SetTimelineParameter(uint32_t aIndex,
-                            const AudioParamTimeline& aValue,
-                            TrackRate aSampleRate) override
+  void RecvTimelineEvent(uint32_t aIndex,
+                         AudioTimelineEvent& aEvent) override
   {
     MOZ_ASSERT(mSource && mDestination);
+
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case FREQUENCY:
-      mFrequency = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination);
+      mFrequency.InsertEvent<int64_t>(aEvent);
       break;
     case DETUNE:
-      mDetune = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mDetune, mSource, mDestination);
+      mDetune.InsertEvent<int64_t>(aEvent);
       break;
     case Q:
-      mQ = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mQ, mSource, mDestination);
+      mQ.InsertEvent<int64_t>(aEvent);
       break;
     case GAIN:
-      mGain = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mGain, mSource, mDestination);
+      mGain.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter");
     }
   }
 
   virtual void ProcessBlock(AudioNodeStream* aStream,
                             GraphTime aFrom,
@@ -342,37 +342,37 @@ BiquadFilterNode::GetFrequencyResponse(c
   double detune = mDetune->GetValueAtTime(currentTime);
 
   WebCore::Biquad biquad;
   SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain, detune);
   biquad.getFrequencyResponse(int(length), frequencies, aMagResponse.Data(), aPhaseResponse.Data());
 }
 
 void
-BiquadFilterNode::SendFrequencyToStream(AudioNode* aNode)
+BiquadFilterNode::SendFrequencyToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineParameterToStream(This, BiquadFilterNodeEngine::FREQUENCY, *This->mFrequency);
+  SendTimelineEventToStream(This, BiquadFilterNodeEngine::FREQUENCY, aEvent);
 }
 
 void
-BiquadFilterNode::SendDetuneToStream(AudioNode* aNode)
+BiquadFilterNode::SendDetuneToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineParameterToStream(This, BiquadFilterNodeEngine::DETUNE, *This->mDetune);
+  SendTimelineEventToStream(This, BiquadFilterNodeEngine::DETUNE, aEvent);
 }
 
 void
-BiquadFilterNode::SendQToStream(AudioNode* aNode)
+BiquadFilterNode::SendQToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineParameterToStream(This, BiquadFilterNodeEngine::Q, *This->mQ);
+  SendTimelineEventToStream(This, BiquadFilterNodeEngine::Q, aEvent);
 }
 
 void
-BiquadFilterNode::SendGainToStream(AudioNode* aNode)
+BiquadFilterNode::SendGainToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineParameterToStream(This, BiquadFilterNodeEngine::GAIN, *This->mGain);
+  SendTimelineEventToStream(This, BiquadFilterNodeEngine::GAIN, aEvent);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/BiquadFilterNode.h
+++ b/dom/media/webaudio/BiquadFilterNode.h
@@ -10,16 +10,17 @@
 #include "AudioNode.h"
 #include "AudioParam.h"
 #include "mozilla/dom/BiquadFilterNodeBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
+struct AudioTimelineEvent;
 
 class BiquadFilterNode final : public AudioNode
 {
 public:
   explicit BiquadFilterNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BiquadFilterNode, AudioNode)
@@ -63,20 +64,24 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~BiquadFilterNode();
 
 private:
-  static void SendFrequencyToStream(AudioNode* aNode);
-  static void SendDetuneToStream(AudioNode* aNode);
-  static void SendQToStream(AudioNode* aNode);
-  static void SendGainToStream(AudioNode* aNode);
+  static void SendFrequencyToStream(AudioNode* aNode,
+                                    const AudioTimelineEvent& aEvent);
+  static void SendDetuneToStream(AudioNode* aNode,
+                                const AudioTimelineEvent& aEvente);
+  static void SendQToStream(AudioNode* aNode,
+                            const AudioTimelineEvent& aEvent);
+  static void SendGainToStream(AudioNode* aNode,
+                               const AudioTimelineEvent& aEvent);
 
 private:
   BiquadFilterType mType;
   nsRefPtr<AudioParam> mFrequency;
   nsRefPtr<AudioParam> mDetune;
   nsRefPtr<AudioParam> mQ;
   nsRefPtr<AudioParam> mGain;
 };
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -55,25 +55,27 @@ public:
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
 
   enum Parameters {
     DELAY,
   };
-  void SetTimelineParameter(uint32_t aIndex,
-                            const AudioParamTimeline& aValue,
-                            TrackRate aSampleRate) override
+  void RecvTimelineEvent(uint32_t aIndex,
+                         AudioTimelineEvent& aEvent) override
   {
+    MOZ_ASSERT(mSource && mDestination);
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case DELAY:
-      MOZ_ASSERT(mSource && mDestination);
-      mDelay = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mDelay, mSource, mDestination);
+      mDelay.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad DelayNodeEngine TimelineParameter");
     }
   }
 
   virtual void ProcessBlock(AudioNodeStream* aStream,
                             GraphTime aFrom,
@@ -232,16 +234,16 @@ DelayNode::SizeOfIncludingThis(MallocSiz
 
 JSObject*
 DelayNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DelayNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-DelayNode::SendDelayToStream(AudioNode* aNode)
+DelayNode::SendDelayToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   DelayNode* This = static_cast<DelayNode*>(aNode);
-  SendTimelineParameterToStream(This, DelayNodeEngine::DELAY, *This->mDelay);
+  SendTimelineEventToStream(This, DelayNodeEngine::DELAY, aEvent);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/DelayNode.h
+++ b/dom/media/webaudio/DelayNode.h
@@ -37,17 +37,18 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~DelayNode();
 
 private:
-  static void SendDelayToStream(AudioNode* aNode);
+  static void SendDelayToStream(AudioNode* aNode,
+                                const AudioTimelineEvent& aEvent);
   friend class DelayNodeEngine;
 
 private:
   nsRefPtr<AudioParam> mDelay;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/DynamicsCompressorNode.cpp
+++ b/dom/media/webaudio/DynamicsCompressorNode.cpp
@@ -56,41 +56,40 @@ public:
 
   enum Parameters {
     THRESHOLD,
     KNEE,
     RATIO,
     ATTACK,
     RELEASE
   };
-  void SetTimelineParameter(uint32_t aIndex,
-                            const AudioParamTimeline& aValue,
-                            TrackRate aSampleRate) override
+  void RecvTimelineEvent(uint32_t aIndex,
+                         AudioTimelineEvent& aEvent) override
   {
     MOZ_ASSERT(mSource && mDestination);
+
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case THRESHOLD:
-      mThreshold = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mThreshold, mSource, mDestination);
+      mThreshold.InsertEvent<int64_t>(aEvent);
       break;
     case KNEE:
-      mKnee = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mKnee, mSource, mDestination);
+      mKnee.InsertEvent<int64_t>(aEvent);
       break;
     case RATIO:
-      mRatio = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mRatio, mSource, mDestination);
+      mRatio.InsertEvent<int64_t>(aEvent);
       break;
     case ATTACK:
-      mAttack = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mAttack, mSource, mDestination);
+      mAttack.InsertEvent<int64_t>(aEvent);
       break;
     case RELEASE:
-      mRelease = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mRelease, mSource, mDestination);
+      mRelease.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad DynamicsCompresssorNodeEngine TimelineParameter");
     }
   }
 
   virtual void ProcessBlock(AudioNodeStream* aStream,
                             GraphTime aFrom,
@@ -233,44 +232,49 @@ DynamicsCompressorNode::SizeOfIncludingT
 
 JSObject*
 DynamicsCompressorNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DynamicsCompressorNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-DynamicsCompressorNode::SendThresholdToStream(AudioNode* aNode)
+DynamicsCompressorNode::SendThresholdToStream(AudioNode* aNode,
+                                              const AudioTimelineEvent& aEvent)
 {
   DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::THRESHOLD, *This->mThreshold);
+  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::THRESHOLD, aEvent);
 }
 
 void
-DynamicsCompressorNode::SendKneeToStream(AudioNode* aNode)
+DynamicsCompressorNode::SendKneeToStream(AudioNode* aNode,
+                                         const AudioTimelineEvent& aEvent)
 {
   DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::KNEE, *This->mKnee);
+  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::KNEE, aEvent);
 }
 
 void
-DynamicsCompressorNode::SendRatioToStream(AudioNode* aNode)
+DynamicsCompressorNode::SendRatioToStream(AudioNode* aNode,
+                                          const AudioTimelineEvent& aEvent)
 {
   DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::RATIO, *This->mRatio);
+  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::RATIO, aEvent);
 }
 
 void
-DynamicsCompressorNode::SendAttackToStream(AudioNode* aNode)
+DynamicsCompressorNode::SendAttackToStream(AudioNode* aNode,
+                                          const AudioTimelineEvent& aEvent)
 {
   DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::ATTACK, *This->mAttack);
+  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::ATTACK, aEvent);
 }
 
 void
-DynamicsCompressorNode::SendReleaseToStream(AudioNode* aNode)
+DynamicsCompressorNode::SendReleaseToStream(AudioNode* aNode,
+                                            const AudioTimelineEvent& aEvent)
 {
   DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::RELEASE, *This->mRelease);
+  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::RELEASE, aEvent);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/DynamicsCompressorNode.h
+++ b/dom/media/webaudio/DynamicsCompressorNode.h
@@ -69,21 +69,26 @@ public:
     MOZ_ASSERT(NS_IsMainThread());
     mReduction = aReduction;
   }
 
 protected:
   virtual ~DynamicsCompressorNode();
 
 private:
-  static void SendThresholdToStream(AudioNode* aNode);
-  static void SendKneeToStream(AudioNode* aNode);
-  static void SendRatioToStream(AudioNode* aNode);
-  static void SendAttackToStream(AudioNode* aNode);
-  static void SendReleaseToStream(AudioNode* aNode);
+  static void SendThresholdToStream(AudioNode* aNode,
+                                    const AudioTimelineEvent& aEvent);
+  static void SendKneeToStream(AudioNode* aNode,
+                               const AudioTimelineEvent& aEvent);
+  static void SendRatioToStream(AudioNode* aNode,
+                                const AudioTimelineEvent& aEvent);
+  static void SendAttackToStream(AudioNode* aNode,
+                                 const AudioTimelineEvent& aEvent);
+  static void SendReleaseToStream(AudioNode* aNode,
+                                  const AudioTimelineEvent& aEvent);
 
 private:
   nsRefPtr<AudioParam> mThreshold;
   nsRefPtr<AudioParam> mKnee;
   nsRefPtr<AudioParam> mRatio;
   float mReduction;
   nsRefPtr<AudioParam> mAttack;
   nsRefPtr<AudioParam> mRelease;
--- a/dom/media/webaudio/GainNode.cpp
+++ b/dom/media/webaudio/GainNode.cpp
@@ -38,25 +38,27 @@ public:
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
 
   enum Parameters {
     GAIN
   };
-  void SetTimelineParameter(uint32_t aIndex,
-                            const AudioParamTimeline& aValue,
-                            TrackRate aSampleRate) override
+  void RecvTimelineEvent(uint32_t aIndex,
+                         AudioTimelineEvent& aEvent) override
   {
+    MOZ_ASSERT(mSource && mDestination);
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case GAIN:
-      MOZ_ASSERT(mSource && mDestination);
-      mGain = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mGain, mSource, mDestination);
+      mGain.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad GainNodeEngine TimelineParameter");
     }
   }
 
   virtual void ProcessBlock(AudioNodeStream* aStream,
                             GraphTime aFrom,
@@ -154,16 +156,16 @@ GainNode::SizeOfIncludingThis(MallocSize
 
 JSObject*
 GainNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return GainNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-GainNode::SendGainToStream(AudioNode* aNode)
+GainNode::SendGainToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   GainNode* This = static_cast<GainNode*>(aNode);
-  SendTimelineParameterToStream(This, GainNodeEngine::GAIN, *This->mGain);
+  SendTimelineEventToStream(This, GainNodeEngine::GAIN, aEvent);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/GainNode.h
+++ b/dom/media/webaudio/GainNode.h
@@ -37,17 +37,17 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~GainNode();
 
 private:
-  static void SendGainToStream(AudioNode* aNode);
+  static void SendGainToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent);
 
 private:
   nsRefPtr<AudioParam> mGain;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -52,31 +52,33 @@ public:
   enum Parameters {
     FREQUENCY,
     DETUNE,
     TYPE,
     PERIODICWAVE,
     START,
     STOP,
   };
-  void SetTimelineParameter(uint32_t aIndex,
-                            const AudioParamTimeline& aValue,
-                            TrackRate aSampleRate) override
+  void RecvTimelineEvent(uint32_t aIndex,
+                         AudioTimelineEvent& aEvent) override
   {
     mRecomputeParameters = true;
+
+    MOZ_ASSERT(mSource && mDestination);
+
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case FREQUENCY:
-      MOZ_ASSERT(mSource && mDestination);
-      mFrequency = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination);
+      mFrequency.InsertEvent<int64_t>(aEvent);
       break;
     case DETUNE:
-      MOZ_ASSERT(mSource && mDestination);
-      mDetune = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mDetune, mSource, mDestination);
+      mDetune.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad OscillatorNodeEngine TimelineParameter");
     }
   }
 
   virtual void SetStreamTimeParameter(uint32_t aIndex, StreamTime aParam) override
   {
@@ -435,33 +437,33 @@ OscillatorNode::DestroyMediaStream()
 {
   if (mStream) {
     mStream->RemoveMainThreadListener(this);
   }
   AudioNode::DestroyMediaStream();
 }
 
 void
-OscillatorNode::SendFrequencyToStream(AudioNode* aNode)
+OscillatorNode::SendFrequencyToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   OscillatorNode* This = static_cast<OscillatorNode*>(aNode);
   if (!This->mStream) {
     return;
   }
-  SendTimelineParameterToStream(This, OscillatorNodeEngine::FREQUENCY, *This->mFrequency);
+  SendTimelineEventToStream(This, OscillatorNodeEngine::FREQUENCY, aEvent);
 }
 
 void
-OscillatorNode::SendDetuneToStream(AudioNode* aNode)
+OscillatorNode::SendDetuneToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   OscillatorNode* This = static_cast<OscillatorNode*>(aNode);
   if (!This->mStream) {
     return;
   }
-  SendTimelineParameterToStream(This, OscillatorNodeEngine::DETUNE, *This->mDetune);
+  SendTimelineEventToStream(This, OscillatorNodeEngine::DETUNE, aEvent);
 }
 
 void
 OscillatorNode::SendTypeToStream()
 {
   if (!mStream) {
     return;
   }
--- a/dom/media/webaudio/OscillatorNode.h
+++ b/dom/media/webaudio/OscillatorNode.h
@@ -82,18 +82,18 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~OscillatorNode();
 
 private:
-  static void SendFrequencyToStream(AudioNode* aNode);
-  static void SendDetuneToStream(AudioNode* aNode);
+  static void SendFrequencyToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent);
+  static void SendDetuneToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent);
   void SendTypeToStream();
   void SendPeriodicWaveToStream();
 
 private:
   OscillatorType mType;
   nsRefPtr<PeriodicWave> mPeriodicWave;
   nsRefPtr<AudioParam> mFrequency;
   nsRefPtr<AudioParam> mDetune;
--- a/dom/media/webaudio/StereoPannerNode.cpp
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -44,25 +44,27 @@ public:
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
 
   enum Parameters {
     PAN
   };
-  void SetTimelineParameter(uint32_t aIndex,
-                            const AudioParamTimeline& aValue,
-                            TrackRate aSampleRate) override
+  void RecvTimelineEvent(uint32_t aIndex,
+                         AudioTimelineEvent& aEvent) override
   {
+    MOZ_ASSERT(mSource && mDestination);
+    WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+                                                    mSource,
+                                                    mDestination);
+
     switch (aIndex) {
     case PAN:
-      MOZ_ASSERT(mSource && mDestination);
-      mPan = aValue;
-      WebAudioUtils::ConvertAudioParamToTicks(mPan, mSource, mDestination);
+      mPan.InsertEvent<int64_t>(aEvent);
       break;
     default:
       NS_ERROR("Bad StereoPannerNode TimelineParameter");
     }
   }
 
   void GetGainValuesForPanning(float aPanning,
                                bool aMonoToStereo,
@@ -207,16 +209,16 @@ StereoPannerNode::SizeOfIncludingThis(Ma
 
 JSObject*
 StereoPannerNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return StereoPannerNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-StereoPannerNode::SendPanToStream(AudioNode* aNode)
+StereoPannerNode::SendPanToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
 {
   StereoPannerNode* This = static_cast<StereoPannerNode*>(aNode);
-  SendTimelineParameterToStream(This, StereoPannerNodeEngine::PAN, *This->mPan);
+  SendTimelineEventToStream(This, StereoPannerNodeEngine::PAN, aEvent);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/StereoPannerNode.h
+++ b/dom/media/webaudio/StereoPannerNode.h
@@ -55,17 +55,18 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~StereoPannerNode();
 
 private:
-  static void SendPanToStream(AudioNode* aNode);
+  static void SendPanToStream(AudioNode* aNode,
+                              const AudioTimelineEvent& aEvent);
   nsRefPtr<AudioParam> mPan;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/webaudio/WebAudioUtils.cpp
+++ b/dom/media/webaudio/WebAudioUtils.cpp
@@ -1,47 +1,31 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebAudioUtils.h"
 #include "AudioNodeStream.h"
-#include "AudioParamTimeline.h"
 #include "blink/HRTFDatabaseLoader.h"
 
 namespace mozilla {
 
 namespace dom {
 
-struct ConvertTimeToTickHelper
-{
-  AudioNodeStream* mSourceStream;
-  AudioNodeStream* mDestinationStream;
-
-  static int64_t Convert(double aTime, void* aClosure)
-  {
-    ConvertTimeToTickHelper* This = static_cast<ConvertTimeToTickHelper*> (aClosure);
-    MOZ_ASSERT(This->mSourceStream->SampleRate() == This->mDestinationStream->SampleRate());
-    return This->mSourceStream->
-      TicksFromDestinationTime(This->mDestinationStream, aTime);
-  }
-};
-
-void
-WebAudioUtils::ConvertAudioParamToTicks(AudioParamTimeline& aParam,
-                                        AudioNodeStream* aSource,
-                                        AudioNodeStream* aDest)
+void WebAudioUtils::ConvertAudioTimelineEventToTicks(AudioTimelineEvent& aEvent,
+                                                     AudioNodeStream* aSource,
+                                                     AudioNodeStream* aDest)
 {
   MOZ_ASSERT(!aSource || aSource->SampleRate() == aDest->SampleRate());
-  ConvertTimeToTickHelper ctth;
-  ctth.mSourceStream = aSource;
-  ctth.mDestinationStream = aDest;
-  aParam.ConvertEventTimesToTicks(ConvertTimeToTickHelper::Convert, &ctth, aDest->SampleRate());
+  aEvent.SetTimeInTicks(
+      aSource->TicksFromDestinationTime(aDest, aEvent.Time<double>()));
+  aEvent.mTimeConstant *= aSource->SampleRate();
+  aEvent.mDuration *= aSource->SampleRate();
 }
 
 void
 WebAudioUtils::Shutdown()
 {
   WebCore::HRTFDatabaseLoader::shutdown();
 }
 
--- a/dom/media/webaudio/WebAudioUtils.h
+++ b/dom/media/webaudio/WebAudioUtils.h
@@ -17,17 +17,17 @@
 typedef struct SpeexResamplerState_ SpeexResamplerState;
 
 namespace mozilla {
 
 class AudioNodeStream;
 
 namespace dom {
 
-class AudioParamTimeline;
+struct AudioTimelineEvent;
 
 namespace WebAudioUtils {
   // 32 is the minimum required by the spec for createBuffer() and
   // createScriptProcessor() and matches what is used by Blink.  The limit
   // protects against large memory allocations.
   const size_t MaxChannelCount = 32;
   // AudioContext::CreateBuffer() "must support sample-rates in at least the
   // range 22050 to 96000."
@@ -50,27 +50,27 @@ namespace WebAudioUtils {
    * over aDuration seconds.
    */
   inline double ComputeSmoothingRate(double aDuration, double aSampleRate)
   {
     return 1.0 - std::exp(-1.0 / (aDuration * aSampleRate));
   }
 
   /**
-   * Converts AudioParamTimeline floating point time values to tick values
-   * with respect to a source and a destination AudioNodeStream.
+   * Converts an AudioTimelineEvent's floating point time values to tick values
+   * with respect to a destination AudioNodeStream.
    *
-   * This needs to be called for each AudioParamTimeline that gets sent to an
-   * AudioNodeEngine on the engine side where the AudioParamTimeline is
-   * received.  This means that such engines need to be aware of their source
-   * and destination streams as well.
+   * This needs to be called for each AudioTimelineEvent that gets sent to an
+   * AudioNodeEngine, on the engine side where the AudioTimlineEvent is
+   * received.  This means that such engines need to be aware of their
+   * destination streams as well.
    */
-  void ConvertAudioParamToTicks(AudioParamTimeline& aParam,
-                                AudioNodeStream* aSource,
-                                AudioNodeStream* aDest);
+  void ConvertAudioTimelineEventToTicks(AudioTimelineEvent& aEvent,
+                                        AudioNodeStream* aSource,
+                                        AudioNodeStream* aDest);
 
   /**
    * Converts a linear value to decibels.  Returns aMinDecibels if the linear
    * value is 0.
    */
   inline float ConvertLinearToDecibels(float aLinearValue, float aMinDecibels)
   {
     return aLinearValue ? 20.0f * std::log10(aLinearValue) : aMinDecibels;
--- a/dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
+++ b/dom/media/webaudio/compiledtest/TestAudioEventTimeline.cpp
@@ -4,16 +4,27 @@
  * 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 "AudioEventTimeline.h"
 #include "TestHarness.h"
 #include <sstream>
 #include <limits>
 
+// Mock the MediaStream class
+namespace mozilla {
+class MediaStream
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream)
+private:
+  ~MediaStream() {
+  };
+};
+}
+
 using namespace mozilla;
 using namespace mozilla::dom;
 using std::numeric_limits;
 
 // Some simple testing primitives
 void ok(bool val, const char* msg)
 {
   if (val) {
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -16,17 +16,16 @@ MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 EXPORTS += [
     'AlignedTArray.h',
     'AudioBlock.h',
-    'AudioContext.h',
     'AudioEventTimeline.h',
     'AudioNodeEngine.h',
     'AudioNodeExternalInputStream.h',
     'AudioNodeStream.h',
     'AudioParamTimeline.h',
     'MediaBufferDecoder.h',
     'ThreeDPoint.h',
     'WebAudioUtils.h',
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2612,17 +2612,17 @@ void nsPluginInstanceOwner::Paint(gfxCon
   event.data.draw.data.bitmap.baseAddr = pluginSurface->Data();
   event.data.draw.data.bitmap.rowBytes = aFrameRect.width * 4;
 
   if (!mInstance)
     return;
 
   mInstance->HandleEvent(&event, nullptr);
 
-  aContext->SetOperator(gfxContext::OPERATOR_SOURCE);
+  aContext->SetOp(gfx::CompositionOp::OP_SOURCE);
   aContext->SetSource(pluginSurface, gfxPoint(aFrameRect.x, aFrameRect.y));
   aContext->Clip(aFrameRect);
   aContext->Paint();
 #endif
 }
 #endif
 
 #if defined(MOZ_X11)
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3211,17 +3211,17 @@ PluginInstanceChild::PaintRectToPlatform
 #else
     NS_RUNTIMEABORT("Surface type not implemented.");
 #endif
 }
 
 void
 PluginInstanceChild::PaintRectToSurface(const nsIntRect& aRect,
                                         gfxASurface* aSurface,
-                                        const gfxRGBA& aColor)
+                                        const Color& aColor)
 {
     // Render using temporary X surface, with copy to image surface
     nsIntRect plPaintRect(aRect);
     nsRefPtr<gfxASurface> renderSurface = aSurface;
 #ifdef MOZ_X11
     if (mIsTransparent && (GetQuirks() & QUIRK_FLASH_EXPOSE_COORD_TRANSLATION)) {
         // Work around a bug in Flash up to 10.1 d51 at least, where expose event
         // top left coordinates within the plugin-rect and not at the drawable
@@ -3237,17 +3237,17 @@ PluginInstanceChild::PaintRectToSurface(
 
     if (mIsTransparent && !CanPaintOnBackground()) {
         RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(renderSurface);
         gfx::Rect rect(plPaintRect.x, plPaintRect.y,
                        plPaintRect.width, plPaintRect.height);
         // Moz2D treats OP_SOURCE operations as unbounded, so we need to
         // clip to the rect that we want to fill:
         dt->PushClipRect(rect);
-        dt->FillRect(rect, ColorPattern(ToColor(aColor)), // aColor is already a device color
+        dt->FillRect(rect, ColorPattern(aColor), // aColor is already a device color
                      DrawOptions(1.f, CompositionOp::OP_SOURCE));
         dt->PopClip();
         dt->Flush();
     }
 
     PaintRectToPlatformSurface(plPaintRect, renderSurface);
 
     if (renderSurface != aSurface) {
@@ -3320,49 +3320,49 @@ PluginInstanceChild::PaintRectWithAlphaE
     // On windows, we need an HDC and so can't paint directly to
     // vanilla image surfaces.  Bifurcate this painting code so that
     // we don't accidentally attempt that.
     if (!SharedDIBSurface::IsSharedDIBSurface(aSurface))
         NS_RUNTIMEABORT("Expected SharedDIBSurface!");
 
     // Paint the plugin directly onto the target, with a white
     // background and copy the result
-    PaintRectToSurface(rect, aSurface, gfxRGBA(1.0, 1.0, 1.0));
+    PaintRectToSurface(rect, aSurface, Color(1.f, 1.f, 1.f));
     {
         RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(whiteImage);
         RefPtr<SourceSurface> surface =
             gfxPlatform::GetSourceSurfaceForSurface(dt, aSurface);
         dt->CopySurface(surface, rect, IntPoint());
     }
 
     // Paint the plugin directly onto the target, with a black
     // background
-    PaintRectToSurface(rect, aSurface, gfxRGBA(0.0, 0.0, 0.0));
+    PaintRectToSurface(rect, aSurface, Color(0.f, 0.f, 0.f));
 
     // Don't copy the result, just extract a subimage so that we can
     // recover alpha directly into the target
     gfxImageSurface *image = static_cast<gfxImageSurface*>(aSurface);
     blackImage = image->GetSubimage(targetRect);
 
 #else
     // Paint onto white background
     whiteImage->SetDeviceOffset(deviceOffset);
-    PaintRectToSurface(rect, whiteImage, gfxRGBA(1.0, 1.0, 1.0));
+    PaintRectToSurface(rect, whiteImage, Color(1.f, 1.f, 1.f));
 
     if (useSurfaceSubimageForBlack) {
         gfxImageSurface *surface = static_cast<gfxImageSurface*>(aSurface);
         blackImage = surface->GetSubimage(targetRect);
     } else {
         blackImage = new gfxImageSurface(targetSize,
                                          gfxImageFormat::ARGB32);
     }
 
     // Paint onto black background
     blackImage->SetDeviceOffset(deviceOffset);
-    PaintRectToSurface(rect, blackImage, gfxRGBA(0.0, 0.0, 0.0));
+    PaintRectToSurface(rect, blackImage, Color(0.f, 0.f, 0.f));
 #endif
 
     MOZ_ASSERT(whiteImage && blackImage, "Didn't paint enough!");
 
     // Extract alpha from black and white image and store to black
     // image
     if (!gfxAlphaRecovery::RecoverAlpha(blackImage, whiteImage)) {
         return;
@@ -3512,34 +3512,34 @@ PluginInstanceChild::ShowPluginFrame()
                 mHelperSurface ? mHelperSurface : mCurrentSurface;
             RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(surface);
             RefPtr<SourceSurface> backgroundSurface =
                 gfxPlatform::GetSourceSurfaceForSurface(dt, mBackground);
             dt->CopySurface(backgroundSurface, rect, rect.TopLeft());
         }
         // ... and hand off to the plugin
         // BEWARE: mBackground may die during this call
-        PaintRectToSurface(rect, mCurrentSurface, gfxRGBA(0.0, 0.0, 0.0, 0.0));
+        PaintRectToSurface(rect, mCurrentSurface, Color());
     } else if (!temporarilyMakeVisible && mDoAlphaExtraction) {
         // We don't want to pay the expense of alpha extraction for
         // phony paints.
         PLUGIN_LOG_DEBUG(("  (with alpha recovery)"));
         PaintRectWithAlphaExtraction(rect, mCurrentSurface);
     } else {
         PLUGIN_LOG_DEBUG(("  (onto opaque surface)"));
 
         // If we're on a platform that needs helper surfaces for
         // plugins, and we're forcing a throwaway paint of a
         // wmode=transparent plugin, then make sure to use the helper
         // surface here.
         nsRefPtr<gfxASurface> target =
             (temporarilyMakeVisible && mHelperSurface) ?
             mHelperSurface : mCurrentSurface;
 
-        PaintRectToSurface(rect, target, gfxRGBA(0.0, 0.0, 0.0, 0.0));
+        PaintRectToSurface(rect, target, Color());
     }
     mHasPainted = true;
 
     if (temporarilyMakeVisible) {
         mWindow.clipRect.right = mWindow.clipRect.bottom = 0;
 
         PLUGIN_LOG_DEBUG(
             ("[InstanceChild][%p] Undoing temporary clipping w=<x=%d,y=%d, w=%d,h=%d>, clip=<l=%d,t=%d,r=%d,b=%d>",
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -509,17 +509,17 @@ private:
 
     // In the PaintRect functions, aSurface is the size of the full plugin
     // window. Each PaintRect function renders into the subrectangle aRect of
     // aSurface (possibly more if we're working around a Flash bug).
 
     // Paint plugin content rectangle to surface with bg color filling
     void PaintRectToSurface(const nsIntRect& aRect,
                             gfxASurface* aSurface,
-                            const gfxRGBA& aColor);
+                            const gfx::Color& aColor);
 
     // Render plugin content to surface using
     // white/black image alpha extraction algorithm
     void PaintRectWithAlphaExtraction(const nsIntRect& aRect,
                                       gfxASurface* aSurface);
 
     // Call plugin NPAPI function to render plugin content to surface
     // @param - aSurface - should be compatible with current platform plugin rendering
--- a/dom/system/gonk/AudioChannelManager.cpp
+++ b/dom/system/gonk/AudioChannelManager.cpp
@@ -11,36 +11,34 @@
 #include "nsIDocShell.h"
 #include "nsIPermissionManager.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "AudioChannelManager.h"
 #include "mozilla/dom/AudioChannelManagerBinding.h"
 #include "mozilla/dom/nsBrowserElement.h"
 #include "mozilla/Services.h"
 
-using namespace mozilla::hal;
-
 namespace mozilla {
 namespace dom {
 namespace system {
 
 NS_IMPL_QUERY_INTERFACE_INHERITED(AudioChannelManager, DOMEventTargetHelper,
                                   nsIDOMEventListener)
 NS_IMPL_ADDREF_INHERITED(AudioChannelManager, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(AudioChannelManager, DOMEventTargetHelper)
 
 AudioChannelManager::AudioChannelManager()
   : mVolumeChannel(-1)
 {
-  RegisterSwitchObserver(SWITCH_HEADPHONES, this);
+  hal::RegisterSwitchObserver(hal::SWITCH_HEADPHONES, this);
 }
 
 AudioChannelManager::~AudioChannelManager()
 {
-  UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
+  hal::UnregisterSwitchObserver(hal::SWITCH_HEADPHONES, this);
 
   nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner());
   NS_ENSURE_TRUE_VOID(target);
 
   target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
                                     this,
                                     /* useCapture = */ true);
 }
@@ -62,17 +60,17 @@ AudioChannelManager::Init(nsPIDOMWindow*
 
 JSObject*
 AudioChannelManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return AudioChannelManagerBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-AudioChannelManager::Notify(const SwitchEvent& aEvent)
+AudioChannelManager::Notify(const hal::SwitchEvent& aEvent)
 {
   mState = Some(aEvent.status());
 
   DispatchTrustedEvent(NS_LITERAL_STRING("headphoneschange"));
 }
 
 bool
 AudioChannelManager::SetVolumeControlChannel(const nsAString& aChannel)
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -43,17 +43,16 @@
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 using namespace mozilla::dom::gonk;
 using namespace android;
-using namespace mozilla::hal;
 using namespace mozilla;
 using namespace mozilla::dom::bluetooth;
 
 #undef LOG
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args)
 
 #define HEADPHONES_STATUS_HEADSET     MOZ_UTF16("headset")
 #define HEADPHONES_STATUS_HEADPHONE   MOZ_UTF16("headphone")
@@ -182,21 +181,21 @@ AudioManager::HandleAudioFlingerDied()
                                   0,
                                   sMaxStreamVolumeTbl[loop]);
     uint32_t index;
     GetStreamVolumeIndex(loop, &index);
     SetStreamVolumeIndex(loop, index);
   }
 
   if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
-    UpdateHeadsetConnectionState(SWITCH_STATE_HEADSET);
+    UpdateHeadsetConnectionState(hal::SWITCH_STATE_HEADSET);
   } else if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
-    UpdateHeadsetConnectionState(SWITCH_STATE_HEADPHONE);
+    UpdateHeadsetConnectionState(hal::SWITCH_STATE_HEADPHONE);
   } else {
-    UpdateHeadsetConnectionState(SWITCH_STATE_OFF);
+    UpdateHeadsetConnectionState(hal::SWITCH_STATE_OFF);
   }
 
   int32_t phoneState = nsIAudioManager::PHONE_STATE_INVALID;
   GetPhoneState(&phoneState);
 #if ANDROID_VERSION < 17
   AudioSystem::setPhoneState(phoneState);
 #else
   AudioSystem::setPhoneState(static_cast<audio_mode_t>(phoneState));
@@ -308,25 +307,25 @@ IsDeviceOn(audio_devices_t device)
 }
 
 NS_IMPL_ISUPPORTS(AudioManager, nsIAudioManager, nsIObserver)
 
 void
 AudioManager::UpdateHeadsetConnectionState(hal::SwitchState aState)
 {
 #if ANDROID_VERSION >= 15
-  if (aState == SWITCH_STATE_HEADSET) {
+  if (aState == hal::SWITCH_STATE_HEADSET) {
     AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
                                           AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
     mHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADSET;
-  } else if (aState == SWITCH_STATE_HEADPHONE) {
+  } else if (aState == hal::SWITCH_STATE_HEADPHONE) {
     AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
                                           AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
     mHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
-  } else if (aState == SWITCH_STATE_OFF) {
+  } else if (aState == hal::SWITCH_STATE_OFF) {
     if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
       AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
                                             AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
     }
     if (mHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
       AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
                                             AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
     }
@@ -505,72 +504,72 @@ AudioManager::Observe(nsISupports* aSubj
     }
   }
 
   NS_WARNING("Unexpected topic in AudioManager");
   return NS_ERROR_FAILURE;
 }
 
 static void
-NotifyHeadphonesStatus(SwitchState aState)
+NotifyHeadphonesStatus(hal::SwitchState aState)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
-    if (aState == SWITCH_STATE_HEADSET) {
+    if (aState == hal::SWITCH_STATE_HEADSET) {
       obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADSET);
-    } else if (aState == SWITCH_STATE_HEADPHONE) {
+    } else if (aState == hal::SWITCH_STATE_HEADPHONE) {
       obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADPHONE);
-    } else if (aState == SWITCH_STATE_OFF) {
+    } else if (aState == hal::SWITCH_STATE_OFF) {
       obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_OFF);
     } else {
       obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_UNKNOWN);
     }
   }
 }
 
-class HeadphoneSwitchObserver : public SwitchObserver
+class HeadphoneSwitchObserver : public hal::SwitchObserver
 {
 public:
   void Notify(const hal::SwitchEvent& aEvent) {
     nsRefPtr<AudioManager> audioManager = AudioManager::GetInstance();
     MOZ_ASSERT(audioManager);
     audioManager->HandleHeadphoneSwitchEvent(aEvent);
   }
 };
 
 void
 AudioManager::HandleHeadphoneSwitchEvent(const hal::SwitchEvent& aEvent)
 {
   NotifyHeadphonesStatus(aEvent.status());
   // When user pulled out the headset, a delay of routing here can avoid the leakage of audio from speaker.
-  if (aEvent.status() == SWITCH_STATE_OFF && mSwitchDone) {
+  if (aEvent.status() == hal::SWITCH_STATE_OFF && mSwitchDone) {
 
     nsRefPtr<AudioManager> self = this;
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableFunction([self]() {
         if (self->mSwitchDone) {
           return;
         }
-        self->UpdateHeadsetConnectionState(SWITCH_STATE_OFF);
+        self->UpdateHeadsetConnectionState(hal::SWITCH_STATE_OFF);
         self->mSwitchDone = true;
     });
     MessageLoop::current()->PostDelayedTask(FROM_HERE, new RunnableCallTask(runnable), 1000);
 
     SwitchProfileData(DEVICE_HEADSET, false);
     mSwitchDone = false;
-  } else if (aEvent.status() != SWITCH_STATE_OFF) {
+  } else if (aEvent.status() != hal::SWITCH_STATE_OFF) {
     UpdateHeadsetConnectionState(aEvent.status());
     SwitchProfileData(DEVICE_HEADSET, true);
     mSwitchDone = true;
   }
   // Handle the coexistence of a2dp / headset device, latest one wins.
 #if ANDROID_VERSION >= 17
   int32_t forceUse = 0;
   GetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, &forceUse);
-  if (aEvent.status() != SWITCH_STATE_OFF && mBluetoothA2dpEnabled) {
+  if (aEvent.status() != hal::SWITCH_STATE_OFF && mBluetoothA2dpEnabled) {
     SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NO_BT_A2DP);
   } else if (forceUse == AUDIO_POLICY_FORCE_NO_BT_A2DP) {
     SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NONE);
   }
 #endif
 }
 
 AudioManager::AudioManager()
@@ -583,20 +582,20 @@ AudioManager::AudioManager()
 #ifdef MOZ_B2G_BT
   , mA2dpSwitchDone(true)
 #endif
   , mObserver(new HeadphoneSwitchObserver())
 #ifdef MOZ_B2G_RIL
   , mMuteCallToRIL(false)
 #endif
 {
-  RegisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
+  hal::RegisterSwitchObserver(hal::SWITCH_HEADPHONES, mObserver);
 
-  UpdateHeadsetConnectionState(GetCurrentSwitchState(SWITCH_HEADPHONES));
-  NotifyHeadphonesStatus(GetCurrentSwitchState(SWITCH_HEADPHONES));
+  UpdateHeadsetConnectionState(hal::GetCurrentSwitchState(hal::SWITCH_HEADPHONES));
+  NotifyHeadphonesStatus(hal::GetCurrentSwitchState(hal::SWITCH_HEADPHONES));
 
   for (uint32_t loop = 0; loop < AUDIO_STREAM_CNT; ++loop) {
     AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(loop), 0,
                                   sMaxStreamVolumeTbl[loop]);
     mCurrentStreamVolumeTbl[loop] = sMaxStreamVolumeTbl[loop];
   }
   // Force publicnotification to output at maximal volume
   SetStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE,
@@ -637,17 +636,17 @@ AudioManager::AudioManager()
   property_get("ro.moz.mute.call.to_ril", value, "false");
   if (!strcmp(value, "true")) {
     mMuteCallToRIL = true;
   }
 #endif
 }
 
 AudioManager::~AudioManager() {
-  UnregisterSwitchObserver(SWITCH_HEADPHONES, mObserver);
+  hal::UnregisterSwitchObserver(hal::SWITCH_HEADPHONES, mObserver);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
   if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID))) {
     NS_WARNING("Failed to remove bluetooth sco status changed observer!");
   }
   if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID))) {
     NS_WARNING("Failed to remove bluetooth a2dp status changed observer!");
@@ -790,17 +789,17 @@ AudioManager::GetFmRadioAudioEnabled(boo
 }
 
 NS_IMETHODIMP
 AudioManager::SetFmRadioAudioEnabled(bool aFmRadioAudioEnabled)
 {
   AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_FM,
     aFmRadioAudioEnabled ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE :
     AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
-  UpdateHeadsetConnectionState(GetCurrentSwitchState(SWITCH_HEADPHONES));
+  UpdateHeadsetConnectionState(hal::GetCurrentSwitchState(hal::SWITCH_HEADPHONES));
   // AUDIO_STREAM_FM is not used on recent gonk.
   // AUDIO_STREAM_MUSIC is used for FM radio volume control.
 #if ANDROID_VERSION < 19
   // sync volume with music after powering on fm radio
   if (aFmRadioAudioEnabled) {
     uint32_t volIndex = mCurrentStreamVolumeTbl[AUDIO_STREAM_MUSIC];
     SetStreamVolumeIndex(AUDIO_STREAM_FM, volIndex);
     mCurrentStreamVolumeTbl[AUDIO_STREAM_FM] = volIndex;
--- a/dom/system/gonk/AutoMounter.cpp
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -35,17 +35,16 @@
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "OpenFileFinder.h"
 #include "Volume.h"
 #include "VolumeManager.h"
 #include "nsIStatusReporter.h"
 
-using namespace mozilla::hal;
 USING_MTP_NAMESPACE
 
 /**************************************************************************
 *
 * The following "switch" files are available for monitoring usb
 * connections:
 *
 *   /sys/devices/virtual/switch/usb_connected/state
@@ -110,30 +109,30 @@ namespace system {
 #define USB_FUNC_DEFAULT    "default"
 
 class AutoMounter;
 
 static void SetAutoMounterStatus(int32_t aStatus);
 
 /***************************************************************************/
 
-inline const char* SwitchStateStr(const SwitchEvent& aEvent)
+inline const char* SwitchStateStr(const hal::SwitchEvent& aEvent)
 {
-  return aEvent.status() == SWITCH_STATE_ON ? "plugged" : "unplugged";
+  return aEvent.status() == hal::SWITCH_STATE_ON ? "plugged" : "unplugged";
 }
 
 /***************************************************************************/
 
 static bool
 IsUsbCablePluggedIn()
 {
 #if 0
   // Use this code when bug 745078 gets fixed (or use whatever the
   // appropriate method is)
-  return GetCurrentSwitchEvent(SWITCH_USB) == SWITCH_STATE_ON;
+  return GetCurrentSwitchEvent(SWITCH_USB) == hal::SWITCH_STATE_ON;
 #else
   // Until then, just go read the file directly
   if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
     char usbState[20];
     if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) {
       DBG("IsUsbCablePluggedIn: state = '%s'", usbState);
       return strcmp(usbState, "CONFIGURED") == 0 ||
              strcmp(usbState, "CONNECTED") == 0;
@@ -1366,32 +1365,32 @@ UsbCableEventIOThread()
 *
 *   Public API
 *
 *   Since the AutoMounter runs in IO Thread context, we need to switch
 *   to IOThread context before we can do anything.
 *
 **************************************************************************/
 
-class UsbCableObserver final : public SwitchObserver
+class UsbCableObserver final : public hal::SwitchObserver
 {
   ~UsbCableObserver()
   {
-    UnregisterSwitchObserver(SWITCH_USB, this);
+    hal::UnregisterSwitchObserver(hal::SWITCH_USB, this);
   }
 
 public:
   NS_INLINE_DECL_REFCOUNTING(UsbCableObserver)
 
   UsbCableObserver()
   {
-    RegisterSwitchObserver(SWITCH_USB, this);
+    hal::RegisterSwitchObserver(hal::SWITCH_USB, this);
   }
 
-  virtual void Notify(const SwitchEvent& aEvent)
+  virtual void Notify(const hal::SwitchEvent& aEvent)
   {
     DBG("UsbCable switch device: %d state: %s\n",
         aEvent.device(), SwitchStateStr(aEvent));
     XRE_GetIOMessageLoop()->PostTask(
         FROM_HERE,
         NewRunnableFunction(UsbCableEventIOThread));
   }
 };
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/beacon/beacon-preflight-handler.sjs
@@ -0,0 +1,39 @@
+function handleRequest(request, response)
+{
+  response.setHeader("Cache-Control", "no-cache, must-revalidate", false);
+
+  if (request.queryString === "verify") {
+    var preflightState = getState("preflight");
+    response.write(preflightState === "done" ? "green" : "red");
+    return;
+  }
+
+  var originHeader = request.getHeader("origin");
+  response.setHeader("Access-Control-Allow-Headers", "content-type", false);
+  response.setHeader("Access-Control-Allow-Methods", "POST, GET", false);
+  response.setHeader("Access-Control-Allow-Origin", originHeader, false);
+  response.setHeader("Access-Control-Allow-Credentials", "true", false);
+
+  if (request.queryString === "beacon") {
+    if (request.method == "OPTIONS") {
+      setState("preflight", "done");
+      response.setStatusLine(null, 200, "OK");
+      return;
+    }
+    response.setStatusLine(null, 200, "OK");
+    response.write("DONE");
+    return;
+  }
+
+  if (request.queryString === "fail") {
+    if (request.method == "OPTIONS") {
+      setState("preflight", "done");
+      response.setStatusLine(null, 400, "Bad Request");
+      return;
+    }
+    setState("preflight", "oops");
+    response.setStatusLine(null, 200, "OK");
+    response.write("DONE");
+    return;
+  }
+}
--- a/dom/tests/mochitest/beacon/mochitest.ini
+++ b/dom/tests/mochitest/beacon/mochitest.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files = beacon-frame.html
                 beacon-handler.sjs
+                beacon-preflight-handler.sjs
                 beacon-originheader-handler.sjs
                 beacon-cors-redirect-handler.sjs
 
 [test_beacon.html]
 [test_beaconFrame.html]
 [test_beaconPreflight.html]
+[test_beaconPreflightFailure.html]
 [test_beaconContentPolicy.html]
 [test_beaconOriginHeader.html]
 [test_beaconCORSRedirect.html]
--- a/dom/tests/mochitest/beacon/test_beaconPreflight.html
+++ b/dom/tests/mochitest/beacon/test_beaconPreflight.html
@@ -12,25 +12,45 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=936340">Mozilla Bug 936340</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-var beaconUrl = "http://mochi.test:8888/tests/dom/tests/mochitest/beacon/beacon-handler.sjs";
+var beaconUrl = "http://example.com/tests/dom/tests/mochitest/beacon/beacon-preflight-handler.sjs?beacon";
+
+var intervalID = null;
+
+function queryIfBeaconSucceeded() {
+  clearInterval(intervalID);
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", "beacon-preflight-handler.sjs?verify", true);
+  xhr.onload = function() {
+    is(xhr.responseText, "green", "SendBeacon should have succeeded after preflight!");
+    SimpleTest.finish();
+  };
+  xhr.onerror = function() {
+    ok(false, "xhr request returned error");
+    SimpleTest.finish();
+  };
+  xhr.send();
+}
 
 // not enabled by default yet.
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({'set': [["beacon.enabled", true]]}, beginTest);
 
 function beginTest() {
   var abv = new Uint8Array([0,1,2,3]);
   var sent = navigator.sendBeacon(beaconUrl, abv);
-  ok(sent, "non-standard con