Merge fx-team to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 18 Sep 2015 13:58:09 -0700
changeset 295866 eb5e1be4923e0c282f1dab34a637c29ff7af0b66
parent 295845 eb30e5ee32f0a11e3df594b5b3e69d0a5566bb63 (current diff)
parent 295865 c4bc60ef00fc319a7af0c95d05fd253d9b3bb811 (diff)
child 295879 e44ee5f2679134c2ff210f93e42c5a86865ade52
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.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 fx-team to central, a=merge
browser/app/profile/firefox.js
browser/devtools/animationinspector/test/browser_animation_iterationCount_hidden_by_default.js
browser/devtools/animationinspector/test/browser_animation_play_pause_button.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_compositor_icon.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_destroy.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_disables_on_finished.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_dont_show_time_after_duration.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_have_control_buttons.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_meta_data.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_scrubber_delayed.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_scrubber_enabled.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_scrubber_moves.js
browser/devtools/animationinspector/test/browser_animation_playerWidgets_state_after_pause.js
browser/devtools/animationinspector/test/browser_animation_rate_select_shows_presets.js
browser/devtools/animationinspector/test/browser_animation_setting_currentTime_works_and_pauses.js
browser/devtools/animationinspector/test/browser_animation_setting_playbackRate_works.js
browser/devtools/animationinspector/test/browser_animation_timeline_displays_with_pref.js
browser/devtools/animationinspector/test/browser_animation_toggle_button_updates_playerWidgets.js
browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_changes.js
browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_rate_changes.js
browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_time_changes.js
mobile/android/base/resources/layout/reading_list_row_view.xml
toolkit/devtools/server/tests/browser/browser_animation_actors_05.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1375,18 +1375,16 @@ pref("devtools.inspector.show_pseudo_ele
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Enable the MDN docs tooltip
 pref("devtools.inspector.mdnDocsTooltip.enabled", true);
-// Show the new animation inspector UI
-pref("devtools.inspector.animationInspectorV3", false);
 
 // DevTools default color unit
 pref("devtools.defaultColorUnit", "hex");
 
 // Enable the Responsive UI tool
 pref("devtools.responsiveUI.no-reload-notification", false);
 
 // Enable the Debugger
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -17,23 +17,18 @@ const kWhitelist = [
    errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i},
   // Tracked in bug 1004428.
   {sourceName: /aboutaccounts\/(main|normalize)\.css/i},
   // TokBox SDK assets, see bug 1032469.
   {sourceName: /loop\/.*sdk-content\/.*\.css$/i},
   // Loop standalone client CSS uses placeholder cross browser pseudo-element
   {sourceName: /loop\/.*\.css/i,
    errorMessage: /Unknown pseudo-class.*placeholder/i},
-  // Loop issues that crept in since this test was broken, see bug ...
-  {sourceName: /loop\/.*shared\/css\/conversation.css/i,
-   errorMessage: /Error in parsing value for 'display'/i},
   {sourceName: /loop\/.*shared\/css\/common.css/i,
    errorMessage: /Unknown property 'user-select'/i},
-  {sourceName: /loop\/.*css\/panel.css/i,
-   errorMessage: /Expected color but found 'none'/i},
   // Highlighter CSS uses chrome-only pseudo-class, see bug 985597.
   {sourceName: /highlighter\.css/i,
    errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i},
 ];
 
 var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
 var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
 
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -1063,17 +1063,16 @@ html[dir="rtl"] .settings-menu .dropdown
   flex-flow: column nowrap;
   background: #fbfbfb;
 }
 
 .fte-get-started-button {
   border: none;
   color: #fff;
   background-color: #00a9dc;
-  border-color: none;
   line-height: 43px;
   margin: 0 15px;
   padding: 0;
   border-radius: 4px;
   font-size: 1.4rem;
   font-weight: bold;
 }
 
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -1381,17 +1381,16 @@ html[dir="rtl"] .room-context-btn-close 
   }
 
   .media-wrapper > .focus-stream > .local ~ .conversation-toolbar {
     max-width: calc(75% - 22px);
   }
 
   .media-wrapper > .focus-stream > .local {
     position: absolute;
-    display:fixed;
     right: 0;
     left: auto;
     /* 30% is the height of the text chat. As we have a margin,
        we don't need to worry about any offset for a border */
     bottom: 0;
     margin: 3px;
     object-fit: contain;
     /* These make the avatar look reasonable and the local
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/blocklists.js
@@ -0,0 +1,204 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+const TEST_LIST = "test-track-simple";
+const TRACK_SUFFIX = "-track-digest256";
+const TRACKING_TABLE_PREF = "urlclassifier.trackingTable";
+const LISTS_PREF_BRANCH = "browser.safebrowsing.provider.mozilla.lists.";
+
+let gBlocklistManager = {
+  _type: "",
+  _blockLists: [],
+  _brandShortName : null,
+  _bundle: null,
+  _tree: null,
+
+  _view: {
+    _rowCount: 0,
+    get rowCount() {
+      return this._rowCount;
+    },
+    getCellText: function (row, column) {
+      if (column.id == "listCol") {
+        let list = gBlocklistManager._blockLists[row];
+        let desc = list.description ? list.description : "";
+        let text = gBlocklistManager._bundle.getFormattedString("mozNameTemplate",
+                                                                [list.name, desc]);
+        return text;
+      }
+      return "";
+    },
+
+    isSeparator: function(index) { return false; },
+    isSorted: function() { return false; },
+    isContainer: function(index) { return false; },
+    setTree: function(tree) {},
+    getImageSrc: function(row, column) {},
+    getProgressMode: function(row, column) {},
+    getCellValue: function(row, column) {
+      if (column.id == "selectionCol")
+        return gBlocklistManager._blockLists[row].selected;
+      return undefined;
+    },
+    cycleHeader: function(column) {},
+    getRowProperties: function(row) { return ""; },
+    getColumnProperties: function(column) { return ""; },
+    getCellProperties: function(row, column) {
+      if (column.id == "selectionCol") {
+        return "checkmark";
+      }
+
+      return "";
+    }
+  },
+
+  onWindowKeyPress: function (event) {
+    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+      window.close();
+    } else if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
+      gBlocklistManager.onApplyChanges();
+    }
+  },
+
+  onLoad: function () {
+    this._bundle = document.getElementById("bundlePreferences");
+    let params = window.arguments[0];
+    this.init(params);
+  },
+
+  init: function (params) {
+    if (this._type) {
+      // reusing an open dialog, clear the old observer
+      this.uninit();
+    }
+
+    this._type = "tracking";
+    this._brandShortName = params.brandShortName;
+
+    let blocklistsText = document.getElementById("blocklistsText");
+    while (blocklistsText.hasChildNodes()) {
+      blocklistsText.removeChild(blocklistsText.firstChild);
+    }
+    blocklistsText.appendChild(document.createTextNode(params.introText));
+
+    document.title = params.windowTitle;
+
+    let treecols = document.getElementsByTagName("treecols")[0];
+    treecols.addEventListener("click", event => {
+      if (event.target.nodeName != "treecol" || event.button != 0) {
+        return;
+      }
+    });
+
+    this._loadBlockLists();
+  },
+
+  uninit: function () {},
+
+  onListSelected: function () {
+    for (let list of this._blockLists) {
+      list.selected = false;
+    }
+    this._blockLists[this._tree.currentIndex].selected = true;
+
+    this._updateTree();
+  },
+
+  onApplyChanges: function () {
+    let activeList = this._getActiveList();
+    let selected = null;
+    for (let list of this._blockLists) {
+      if (list.selected) {
+        selected = list;
+        break;
+      }
+    }
+
+    if (activeList !== selected.id) {
+      const Cc = Components.classes, Ci = Components.interfaces;
+      let msg = this._bundle.getFormattedString("blocklistChangeRequiresRestart",
+                                                [this._brandShortName]);
+      let title = this._bundle.getFormattedString("shouldRestartTitle",
+                                                  [this._brandShortName]);
+      let shouldProceed = Services.prompt.confirm(window, title, msg);
+      if (shouldProceed) {
+        let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+                           .createInstance(Ci.nsISupportsPRBool);
+        Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+                                     "restart");
+        shouldProceed = !cancelQuit.data;
+
+        if (shouldProceed) {
+          let trackingTable = TEST_LIST + "," + selected.id + TRACK_SUFFIX;
+          Services.prefs.setCharPref(TRACKING_TABLE_PREF, trackingTable);
+
+          Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |
+                                Ci.nsIAppStartup.eRestart);
+        }
+      }
+
+      // Don't close the dialog in case we didn't quit.
+      return;
+    }
+    window.close();
+  },
+
+  _loadBlockLists: function () {
+    this._blockLists = [];
+
+    // Load blocklists into a table.
+    let branch = Services.prefs.getBranch(LISTS_PREF_BRANCH);
+    let itemArray = branch.getChildList("");
+    for (let itemName of itemArray) {
+      try {
+        this._createOrUpdateBlockList(itemName);
+      } catch (e) {
+        // Ignore bogus or missing list name.
+        continue;
+      }
+    }
+
+    this._updateTree();
+  },
+
+  _createOrUpdateBlockList: function (itemName) {
+    let branch = Services.prefs.getBranch(LISTS_PREF_BRANCH);
+    let key = branch.getCharPref(itemName);
+    let value = this._bundle.getString(key);
+
+    let suffix = itemName.slice(itemName.lastIndexOf("."));
+    let id = itemName.replace(suffix, "");
+    let list = this._blockLists.find(el => el.id === id);
+    if (!list) {
+      list = { id };
+      this._blockLists.push(list);
+    }
+    list.selected = this._getActiveList() === id;
+
+    // Get the property name from the suffix (e.g. ".name" -> "name").
+    let prop = suffix.slice(1);
+    list[prop] = value;
+
+    return list;
+  },
+
+  _updateTree: function () {
+    this._tree = document.getElementById("blocklistsTree");
+    this._view._rowCount = this._blockLists.length;
+    this._tree.view = this._view;
+  },
+
+  _getActiveList: function () {
+    let activeList = Services.prefs.getCharPref(TRACKING_TABLE_PREF);
+    activeList = activeList.replace(TEST_LIST, "");
+    activeList = activeList.replace(",", "");
+    activeList = activeList.replace(TRACK_SUFFIX, "");
+    return activeList.trim();
+  }
+};
+
+function initWithParams(params) {
+  gBlocklistManager.init(params);
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/blocklists.xul
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/blocklists.dtd" >
+
+<window id="BlocklistsDialog" class="windowDialog"
+        windowtype="Browser:Blocklists"
+        title="&window.title;"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        style="width: &window.width;;"
+        onload="gBlocklistManager.onLoad();"
+        onunload="gBlocklistManager.uninit();"
+        persist="screenX screenY width height"
+        onkeypress="gBlocklistManager.onWindowKeyPress(event);">
+
+  <script src="chrome://global/content/treeUtils.js"/>
+  <script src="chrome://browser/content/preferences/blocklists.js"/>
+
+  <stringbundle id="bundlePreferences"
+                src="chrome://browser/locale/preferences/preferences.properties"/>
+
+  <keyset>
+    <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+  </keyset>
+
+  <vbox class="contentPane largeDialogContainer" flex="1">
+    <description id="blocklistsText" control="url"/>
+    <separator class="thin"/>
+    <tree id="blocklistsTree" flex="1" style="height: 18em;"
+          hidecolumnpicker="true"
+          onselect="gBlocklistManager.onListSelected();">
+      <treecols>
+        <treecol id="selectionCol" label="" flex="1" sortable="false"
+                 type="checkbox"/>
+        <treecol id="listCol" label="&treehead.list.label;" flex="80"
+                 sortable="false"/>
+      </treecols>
+      <treechildren/>
+    </tree>
+  </vbox>
+  <vbox>
+    <spacer flex="1"/>
+    <hbox class="actionButtons" align="right" flex="1">
+      <button oncommand="close();" icon="close"
+              label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
+      <button id="btnApplyChanges" oncommand="gBlocklistManager.onApplyChanges();" icon="save"
+              label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
+    </hbox>
+  </vbox>
+</window>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -111,16 +111,18 @@ var gPrivacyPane = {
     setEventListener("privateBrowsingAutoStart", "command",
                      gPrivacyPane.updateAutostart);
     setEventListener("cookieExceptions", "command",
                      gPrivacyPane.showCookieExceptions);
     setEventListener("showCookiesButton", "command",
                      gPrivacyPane.showCookies);
     setEventListener("clearDataSettings", "command",
                      gPrivacyPane.showClearPrivateDataSettings);
+    setEventListener("changeBlockList", "command",
+                     gPrivacyPane.showBlockLists);
   },
 
   // HISTORY MODE
 
   /**
    * The list of preferences which affect the initial history mode settings.
    * If the auto start private browsing mode pref is active, the initial
    * history mode would be set to "Don't remember anything".
@@ -360,16 +362,31 @@ var gPrivacyPane = {
       }
       pref.value = autoStart.hasAttribute('checked');
       mode.selectedIndex = this._lastMode;
       mode.doCommand();
 
       this._shouldPromptForRestart = true;
   },
 
+  /**
+   * Displays the available block lists for tracking protection.
+   */
+  showBlockLists: function ()
+  {
+    var bundlePreferences = document.getElementById("bundlePreferences");
+    let brandName = document.getElementById("bundleBrand")
+                            .getString("brandShortName");
+    var params = { brandShortName: brandName,
+                   windowTitle: bundlePreferences.getString("blockliststitle"),
+                   introText: bundlePreferences.getString("blockliststext") };
+    gSubDialog.open("chrome://browser/content/preferences/blocklists.xul",
+                    null, params);
+  },
+
   // HISTORY
 
   /*
    * Preferences:
    *
    * places.history.enabled
    * - whether history is enabled or not
    * browser.formfill.enable
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -22,16 +22,19 @@
 
   <!-- XXX button prefs -->
   <preference id="pref.privacy.disable_button.cookie_exceptions"
               name="pref.privacy.disable_button.cookie_exceptions"
               type="bool"/>
   <preference id="pref.privacy.disable_button.view_cookies"
               name="pref.privacy.disable_button.view_cookies"
               type="bool"/>
+  <preference id="pref.privacy.disable_button.change_blocklist"
+              name="pref.privacy.disable_button.change_blocklist"
+              type="bool"/>
 
   <!-- Location Bar -->
   <preference id="browser.urlbar.autocomplete.enabled"
               name="browser.urlbar.autocomplete.enabled"
               type="bool"/>
   <preference id="browser.urlbar.suggest.bookmark"
               name="browser.urlbar.suggest.bookmark"
               type="bool"/>
@@ -76,17 +79,17 @@
 <hbox id="header-privacy"
       class="header"
       hidden="true"
       data-category="panePrivacy">
   <label class="header-name">&panePrivacy.title;</label>
 </hbox>
 
 <!-- Tracking -->
-<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
+<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
   <caption><label>&tracking.label;</label></caption>
   <vbox>
     <hbox align="center">
       <checkbox id="privacyDoNotTrackCheckbox"
                 label="&dntTrackingNotOkay4.label;"
                 accesskey="&dntTrackingNotOkay4.accesskey;"
                 preference="privacy.donottrackheader.enabled"/>
       <label id="doNotTrackInfo"
@@ -111,16 +114,20 @@
     <hbox align="center">
       <checkbox id="trackingProtectionPBM"
                 preference="privacy.trackingprotection.pbmode.enabled"
                 accesskey="&trackingProtectionPBM5.accesskey;"
                 label="&trackingProtectionPBM5.label;" />
       <label id="trackingProtectionPBMLearnMore"
              class="text-link"
              value="&trackingProtectionPBMLearnMore.label;"/>
+      <spacer flex="1" />
+      <button id="changeBlockList"
+              label="&changeBlockList.label;" accesskey="&changeBlockList.accesskey;"
+              preference="pref.privacy.disable_button.change_blocklist"/>
     </hbox>
   </vbox>
 </groupbox>
 
 <!-- History -->
 <groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
   <caption><label>&history.label;</label></caption>
   <hbox align="center">
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -4,16 +4,18 @@
 
 browser.jar:
     content/browser/preferences/aboutPermissions.xul
     content/browser/preferences/aboutPermissions.js
     content/browser/preferences/aboutPermissions.css
     content/browser/preferences/aboutPermissions.xml
     content/browser/preferences/applicationManager.xul
 *   content/browser/preferences/applicationManager.js
+    content/browser/preferences/blocklists.xul
+    content/browser/preferences/blocklists.js
 *   content/browser/preferences/colors.xul
 *   content/browser/preferences/cookies.xul
 *   content/browser/preferences/cookies.js
 *   content/browser/preferences/connection.xul
     content/browser/preferences/connection.js
 *   content/browser/preferences/fonts.xul
     content/browser/preferences/fonts.js
     content/browser/preferences/handlers.xml
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1041,17 +1041,17 @@
                      value="&searchAfter.label;"/>
         </xul:hbox>
       </xul:deck>
       <xul:description anonid="search-panel-one-offs"
                        role="group"
                        class="search-panel-one-offs"/>
       <xul:vbox anonid="add-engines"/>
       <xul:button anonid="search-settings"
-                  oncommand="BrowserUITelemetry.countSearchSettingsEvent('searchbar');openPreferences('paneSearch')"
+                  oncommand="showSettings();"
                   class="search-setting-button search-panel-header"
                   label="&changeSearchSettings.button;"/>
     </content>
     <implementation>
       <!-- Popup rollup is triggered by native events before the mousedown event
            reaches the DOM. The will be set to true by the popuphiding event and
            false after the mousedown event has been triggered to detect what
            caused rollup. -->
@@ -1086,16 +1086,26 @@
           let headerText = this.bundle.formatStringFromName("searchHeader",
                                                             [currentEngine.name], 1);
           document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine-name")
                   .setAttribute("value", headerText);
           document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine")
                   .engine = currentEngine;
         ]]></body>
       </method>
+
+      <method name="showSettings">
+        <body><![CDATA[
+          BrowserUITelemetry.countSearchSettingsEvent("searchbar");
+          openPreferences("paneSearch");
+          // If the preference tab was already selected, the panel doesn't
+          // close itself automatically.
+          BrowserSearch.searchBar._textbox.closePopup();
+        ]]></body>
+      </method>
     </implementation>
     <handlers>
       <handler event="popuphidden"><![CDATA[
         Services.tm.mainThread.dispatch(function() {
           document.getElementById("searchbar").textbox.selectedButton = null;
         }, Ci.nsIThread.DISPATCH_NORMAL);
       ]]></handler>
 
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -215,22 +215,16 @@ this.UITour = {
     ["quit",        {query: "#PanelUI-quit"}],
     ["readerMode-urlBar", {query: "#reader-mode-button"}],
     ["search",      {
       infoPanelOffsetX: 18,
       infoPanelPosition: "after_start",
       query: "#searchbar",
       widgetName: "search-container",
     }],
-    ["searchProvider", {
-      query: (aDocument) => {
-        return null;
-      },
-      widgetName: "search-container",
-    }],
     ["searchIcon", {
       query: (aDocument) => {
         let searchbar = aDocument.getElementById("searchbar");
         return aDocument.getAnonymousElementByAttribute(searchbar,
                                                         "anonid",
                                                         "searchbar-search-button");
       },
       widgetName: "search-container",
@@ -982,21 +976,16 @@ this.UITour = {
     log.debug("getTarget:", aTargetName);
     let deferred = Promise.defer();
     if (typeof aTargetName != "string" || !aTargetName) {
       log.warn("getTarget: Invalid target name specified");
       deferred.reject("Invalid target name specified");
       return deferred.promise;
     }
 
-    if (aTargetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
-      let engineID = aTargetName.slice(TARGET_SEARCHENGINE_PREFIX.length);
-      return this.getSearchEngineTarget(aWindow, engineID);
-    }
-
     let targetObject = this.targets.get(aTargetName);
     if (!targetObject) {
       log.warn("getTarget: The specified target name is not in the allowed set");
       deferred.reject("The specified target name is not in the allowed set");
       return deferred.promise;
     }
 
     let targetQuery = targetObject.query;
@@ -1326,28 +1315,16 @@ this.UITour = {
    *                      are in a sub-frame so the defaultView is not the same as the chrome
    *                      window.
    * @param aTarget    The element to highlight.
    * @param aEffect    (optional) The effect to use from UITour.highlightEffects or "none".
    * @see UITour.highlightEffects
    */
   showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
     function showHighlightPanel() {
-      if (aTarget.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
-        // This won't affect normal higlights done via the panel, so we need to
-        // manually hide those.
-        this.hideHighlight(aChromeWindow);
-        aTarget.node.setAttribute("_moz-menuactive", true);
-        return;
-      }
-
-      // Conversely, highlights for search engines are highlighted via CSS
-      // rather than a panel, so need to be manually removed.
-      this._hideSearchEngineHighlight(aChromeWindow);
-
       let highlighter = aChromeWindow.document.getElementById("UITourHighlight");
 
       let effect = aEffect;
       if (effect == "random") {
         // Exclude "random" from the randomly selected effects.
         let randomEffect = 1 + Math.floor(Math.random() * (this.highlightEffects.length - 1));
         if (randomEffect == this.highlightEffects.length)
           randomEffect--; // On the order of 1 in 2^62 chance of this happening.
@@ -1414,34 +1391,16 @@ this.UITour = {
 
   hideHighlight: function(aWindow) {
     let highlighter = aWindow.document.getElementById("UITourHighlight");
     this._removeAnnotationPanelMutationObserver(highlighter.parentElement);
     highlighter.parentElement.hidePopup();
     highlighter.removeAttribute("active");
 
     this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
-    this._hideSearchEngineHighlight(aWindow);
-  },
-
-  _hideSearchEngineHighlight: function(aWindow) {
-    // We special case highlighting items in the search engines dropdown,
-    // so just blindly remove any highlight there.
-    let searchMenuBtn = null;
-    try {
-      searchMenuBtn = this.targets.get("searchProvider").query(aWindow.document);
-    } catch (e) { /* This is ok to fail. */ }
-    if (searchMenuBtn) {
-      let searchPopup = aWindow.document
-                               .getAnonymousElementByAttribute(searchMenuBtn,
-                                                               "anonid",
-                                                               "searchbar-popup");
-      for (let menuItem of searchPopup.children)
-        menuItem.removeAttribute("_moz-menuactive");
-    }
   },
 
   /**
    * Show an info panel.
    *
    * @param {ChromeWindow} aChromeWindow
    * @param {nsIMessageSender} aMessageManager
    * @param {Node}     aAnchor
@@ -1553,21 +1512,16 @@ this.UITour = {
     }
 
     // Prevent showing a panel at an undefined position.
     if (!this.isElementVisible(aAnchor.node)) {
       log.warn("showInfo: Not showing since the target isn't visible", aAnchor);
       return;
     }
 
-    // Due to a platform limitation, we can't anchor a panel to an element in a
-    // <menupopup>. So we can't support showing info panels for search engines.
-    if (aAnchor.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX))
-      return;
-
     this._setAppMenuStateForAnnotation(aChromeWindow, "info",
                                        this.targetIsInAppMenu(aAnchor),
                                        showInfoPanel.bind(this, this._correctAnchor(aAnchor.node)));
   },
 
   isInfoOnTarget(aChromeWindow, aTargetName) {
     let document = aChromeWindow.document;
     let tooltip = document.getElementById("UITourTooltip");
@@ -1665,20 +1619,16 @@ this.UITour = {
       // is already open.
       aWindow.LoopUI.openCallPanel({ target: toolbarButton.node, }, "rooms").then(() => {
         if (aOpenCallback) {
           aOpenCallback();
         }
       });
       panel.addEventListener("popuphidden", this.onPanelHidden);
       panel.addEventListener("popuphiding", this.hideLoopPanelAnnotations);
-    } else if (aMenuName == "searchEngines") {
-      this.getTarget(aWindow, "searchProvider").then(target => {
-        openMenuButton(target.node);
-      }).catch(log.error);
     } else if (aMenuName == "pocket") {
       this.getTarget(aWindow, "pocket").then(Task.async(function* onPocketTarget(target) {
         let widgetGroupWrapper = CustomizableUI.getWidget(target.widgetName);
         if (widgetGroupWrapper.type != "view" || !widgetGroupWrapper.viewId) {
           log.error("Can't open the pocket menu without a view");
           return;
         }
         let placement = CustomizableUI.getPlacementOfWidget(target.widgetName);
@@ -1730,19 +1680,16 @@ this.UITour = {
       let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
       closeMenuButton(menuBtn);
     } else if (aMenuName == "controlCenter") {
       let panel = aWindow.gIdentityHandler._identityPopup;
       panel.hidePopup();
     } else if (aMenuName == "loop") {
       let panel = aWindow.document.getElementById("loop-notification-panel");
       panel.hidePopup();
-    } else if (aMenuName == "searchEngines") {
-      let menuBtn = this.targets.get("searchProvider").query(aWindow.document);
-      closeMenuButton(menuBtn);
     }
   },
 
   hideAnnotationsForPanel: function(aEvent, aTargetPositionCallback) {
     let win = aEvent.target.ownerDocument.defaultView;
     let annotationElements = new Map([
       // [annotationElement (panel), method to hide the annotation]
       [win.document.getElementById("UITourHighlightContainer"), UITour.hideHighlight.bind(UITour)],
@@ -1877,27 +1824,32 @@ this.UITour = {
       case "availableTargets":
         this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
         break;
       case "loop":
         this.sendPageCallback(aMessageManager, aCallbackID, {
           gettingStartedSeen: Services.prefs.getBoolPref("loop.gettingStarted.seen"),
         });
         break;
+      case "search":
       case "selectedSearchEngine":
         Services.search.init(rv => {
-          let engine;
+          let data;
           if (Components.isSuccessCode(rv)) {
-            engine = Services.search.defaultEngine;
+            let engines = Services.search.getVisibleEngines();
+            data = {
+              searchEngineIdentifier: Services.search.defaultEngine.identifier,
+              engines: [TARGET_SEARCHENGINE_PREFIX + engine.identifier
+                        for (engine of engines)
+                          if (engine.identifier)]
+            };
           } else {
-            engine = { identifier: "" };
+            data = {engines: [], searchEngineIdentifier: ""};
           }
-          this.sendPageCallback(aMessageManager, aCallbackID, {
-            searchEngineIdentifier: engine.identifier
-          });
+          this.sendPageCallback(aMessageManager, aCallbackID, data);
         });
         break;
       case "sync":
         this.sendPageCallback(aMessageManager, aCallbackID, {
           setup: Services.prefs.prefHasUserValue("services.sync.username"),
         });
         break;
       default:
@@ -1945,20 +1897,16 @@ this.UITour = {
       let targetObjects = yield Promise.all(promises);
 
       let targetNames = [];
       for (let targetObject of targetObjects) {
         if (targetObject.node)
           targetNames.push(targetObject.targetName);
       }
 
-      targetNames = targetNames.concat(
-        yield this.getAvailableSearchEngineTargets(window)
-      );
-
       data = {
         targets: targetNames,
       };
       this.availableTargetsCache.set(window, data);
       this.sendPageCallback(aMessageManager, aCallbackID, data);
     }.bind(this)).catch(err => {
       log.error(err);
       this.sendPageCallback(aMessageManager, aCallbackID, {
@@ -2055,65 +2003,16 @@ this.UITour = {
             return resolve();
           }
         }
         reject("selectSearchEngine could not find engine with given ID");
       });
     });
   },
 
-  getAvailableSearchEngineTargets(aWindow) {
-    return new Promise(resolve => {
-      this.getTarget(aWindow, "search").then(searchTarget => {
-        if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
-          return resolve([]);
-
-        Services.search.init(() => {
-          let engines = Services.search.getVisibleEngines();
-          resolve([TARGET_SEARCHENGINE_PREFIX + engine.identifier
-                   for (engine of engines)
-                   if (engine.identifier)]);
-        });
-      }).catch(() => resolve([]));
-    });
-  },
-
-  // We only allow matching based on a search engine's identifier - this gives
-  // us a non-changing ID and guarentees we only match against app-provided
-  // engines.
-  getSearchEngineTarget(aWindow, aIdentifier) {
-    return new Promise((resolve, reject) => {
-      Task.spawn(function*() {
-        let searchTarget = yield this.getTarget(aWindow, "search");
-        // We're not supporting having the searchbar in the app-menu, because
-        // popups within popups gets crazy. This restriction should be lifted
-        // once bug 988151 is implemented, as the page can then be responsible
-        // for opening each menu when appropriate.
-        if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
-          return reject("Search engine not available");
-
-        yield Services.search.init();
-
-        let searchPopup = searchTarget.node._popup;
-        for (let engineNode of searchPopup.children) {
-          let engine = engineNode.engine;
-          if (engine && engine.identifier == aIdentifier) {
-            return resolve({
-              targetName: TARGET_SEARCHENGINE_PREFIX + engine.identifier,
-              node: engineNode,
-            });
-          }
-        }
-        reject("Search engine not available");
-      }.bind(this)).catch(() => {
-        reject("Search engine not available");
-      });
-    });
-  },
-
   notify(eventName, params) {
     let winEnum = Services.wm.getEnumerator("navigator:browser");
     while (winEnum.hasMoreElements()) {
       let window = winEnum.getNext();
       if (window.closed)
         continue;
 
       let openTourBrowsers = this.tourBrowsersByWindow.get(window);
--- a/browser/components/uitour/test/browser_UITour.js
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -211,66 +211,16 @@ var tests = [
     }
 
     let highlight = document.getElementById("UITourHighlight");
     is_element_hidden(highlight, "Highlight should initially be hidden");
 
     gContentAPI.showHighlight("urlbar");
     waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
   },
-  function test_highlight_search_engine(done) {
-    let highlight = document.getElementById("UITourHighlight");
-    gContentAPI.showHighlight("urlbar");
-    waitForElementToBeVisible(highlight, () => {
-
-      let searchbar = document.getElementById("searchbar");
-      done();
-      return; // The oneoffui removes the menu that's being tested here.
-
-      gContentAPI.showMenu("searchEngines", function() {
-        isnot(searchbar, null, "Should have found searchbar");
-        let searchPopup = document.getAnonymousElementByAttribute(searchbar,
-                                                                   "anonid",
-                                                                   "searchbar-popup");
-        isnot(searchPopup, null, "Should have found search popup");
-
-        function getEngineNode(identifier) {
-          let engineNode = null;
-          for (let node of searchPopup.children) {
-            if (node.engine.identifier == identifier) {
-              engineNode = node;
-              break;
-            }
-          }
-          isnot(engineNode, null, "Should have found search engine node in popup");
-          return engineNode;
-        }
-        let googleEngineNode = getEngineNode("google");
-        let bingEngineNode = getEngineNode("bing");
-
-        gContentAPI.showHighlight("searchEngine-google");
-        waitForCondition(() => googleEngineNode.getAttribute("_moz-menuactive") == "true", function() {
-          is_element_hidden(highlight, "Highlight panel should be hidden by highlighting search engine");
-
-          gContentAPI.showHighlight("searchEngine-bing");
-          waitForCondition(() => bingEngineNode.getAttribute("_moz-menuactive") == "true", function() {
-            isnot(googleEngineNode.getAttribute("_moz-menuactive"), "true", "Previous engine should no longer be highlighted");
-
-            gContentAPI.hideHighlight();
-            waitForCondition(() => bingEngineNode.getAttribute("_moz-menuactive") != "true", function() {
-              gContentAPI.hideMenu("searchEngines");
-              waitForCondition(() => searchPopup.state == "closed", function() {
-                done();
-              }, "Search dropdown should close");
-            }, "Menu item should get attribute removed");
-          }, "Menu item should get attribute to make it look active");
-        });
-      });
-    });
-  },
   function test_highlight_effect_unsupported(done) {
     function checkUnsupportedEffect() {
       is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
       done();
     }
 
     let highlight = document.getElementById("UITourHighlight");
     is_element_hidden(highlight, "Highlight should initially be hidden");
@@ -362,27 +312,39 @@ var tests = [
 
           // Cleanup
           CustomizableUI.removeWidgetFromArea("panic-button");
           done();
         });
       });
     });
   },
-  function test_select_search_engine(done) {
+  function test_search(done) {
     Services.search.init(rv => {
       if (!Components.isSuccessCode(rv)) {
         ok(false, "search service init failed: " + rv);
         done();
         return;
       }
       let defaultEngine = Services.search.defaultEngine;
-      gContentAPI.getConfiguration("availableTargets", data => {
-        let searchEngines = data.targets.filter(t => t.startsWith("searchEngine-"));
-        let someOtherEngineID = searchEngines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
+      gContentAPI.getConfiguration("search", data => {
+        let visibleEngines = Services.search.getVisibleEngines();
+        let expectedEngines = ["searchEngine-" + engine.identifier
+                               for (engine of visibleEngines)
+                                 if (engine.identifier)];
+
+        let engines = data.engines;
+        ok(Array.isArray(engines), "data.engines should be an array");
+        is(engines.sort().toString(), expectedEngines.sort().toString(),
+           "Engines should be as expected");
+
+        is(data.searchEngineIdentifier, defaultEngine.identifier,
+           "the searchEngineIdentifier property should contain the defaultEngine's identifier");
+
+        let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
         someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
 
         let observe = function (subject, topic, verb) {
           info("browser-search-engine-modified: " + verb);
           if (verb == "engine-current") {
             is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
             done();
           }
--- a/browser/components/uitour/test/browser_UITour_availableTargets.js
+++ b/browser/components/uitour/test/browser_UITour_availableTargets.js
@@ -29,23 +29,16 @@ if (Services.prefs.getBoolPref("browser.
   }
 }
 
 function test() {
   requestLongerTimeout(2);
   UITourTest();
 }
 
-function searchEngineTargets() {
-  let engines = Services.search.getVisibleEngines();
-  return ["searchEngine-" + engine.identifier
-          for (engine of engines)
-          if (engine.identifier)];
-}
-
 var tests = [
   function test_availableTargets(done) {
     gContentAPI.getConfiguration("availableTargets", (data) => {
       ok_targets(data, [
         "accountStatus",
         "addons",
         "appMenu",
         "backForward",
@@ -58,17 +51,16 @@ var tests = [
         ...(hasPocket ? ["pocket"] : []),
         "privateWindow",
         "quit",
         "readerMode-urlBar",
         "search",
         "searchIcon",
         "trackingProtection",
         "urlbar",
-        ...searchEngineTargets(),
         ...(hasWebIDE ? ["webide"] : [])
       ]);
 
       ok(UITour.availableTargetsCache.has(window),
          "Targets should now be cached");
       done();
     });
   },
@@ -91,17 +83,16 @@ var tests = [
         ...(hasPocket ? ["pocket"] : []),
         "privateWindow",
         "quit",
         "readerMode-urlBar",
         "search",
         "searchIcon",
         "trackingProtection",
         "urlbar",
-        ...searchEngineTargets(),
         ...(hasWebIDE ? ["webide"] : [])
       ]);
 
       ok(UITour.availableTargetsCache.has(window),
          "Targets should now be cached again");
       CustomizableUI.reset();
       ok(!UITour.availableTargetsCache.has(window),
          "Targets should not be cached after reset");
@@ -109,17 +100,17 @@ var tests = [
     });
   },
 
   function test_availableTargets_exceptionFromGetTarget(done) {
     // The query function for the "search" target will throw if it's not found.
     // Make sure the callback still fires with the other available targets.
     CustomizableUI.removeWidgetFromArea("search-container");
     gContentAPI.getConfiguration("availableTargets", (data) => {
-      // Default minus "search" and "searchProvider" and "searchIcon"
+      // Default minus "search" and "searchIcon"
       ok_targets(data, [
         "accountStatus",
         "addons",
         "appMenu",
         "backForward",
         "bookmarks",
         "customize",
         "help",
--- a/browser/devtools/animationinspector/animation-controller.js
+++ b/browser/devtools/animationinspector/animation-controller.js
@@ -18,17 +18,16 @@ Cu.import("resource:///modules/devtools/
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
                                "devtools/toolkit/event-emitter");
 loader.lazyRequireGetter(this, "AnimationsFront",
                                "devtools/server/actors/animation", true);
 
 const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
-const V3_UI_PREF = "devtools.inspector.animationInspectorV3";
 
 // Global toolbox/inspector, set when startup is called.
 var gToolbox, gInspector;
 
 /**
  * Startup the animationinspector controller and view, called by the sidebar
  * widget when loading/unloading the iframe into the tab.
  */
@@ -75,16 +74,18 @@ function destroy() {
  * features should be enabled/disabled.
  * @param {Target} target The current toolbox target.
  * @return {Object} An object with boolean properties.
  */
 var getServerTraits = Task.async(function*(target) {
   let config = [
     { name: "hasToggleAll", actor: "animations",
       method: "toggleAll" },
+    { name: "hasToggleSeveral", actor: "animations",
+      method: "toggleSeveral" },
     { name: "hasSetCurrentTime", actor: "animationplayer",
       method: "setCurrentTime" },
     { name: "hasMutationEvents", actor: "animations",
      method: "stopAnimationPlayerUpdates" },
     { name: "hasSetPlaybackRate", actor: "animationplayer",
       method: "setPlaybackRate" },
     { name: "hasTargetNode", actor: "domwalker",
       method: "getNodeFromActor" },
@@ -92,33 +93,27 @@ var getServerTraits = Task.async(functio
       method: "setCurrentTimes" }
   ];
 
   let traits = {};
   for (let {name, actor, method} of config) {
     traits[name] = yield target.actorHasMethod(actor, method);
   }
 
-  // Special pref-based UI trait.
-  traits.isNewUI = Services.prefs.getBoolPref(V3_UI_PREF);
-
   return traits;
 });
 
 /**
  * The animationinspector controller's job is to retrieve AnimationPlayerFronts
  * from the server. It is also responsible for keeping the list of players up to
  * date when the node selection changes in the inspector, as well as making sure
  * no updates are done when the animationinspector sidebar panel is not visible.
  *
  * AnimationPlayerFronts are available in AnimationsController.animationPlayers.
  *
- * Note also that all AnimationPlayerFronts handled by the controller are set to
- * auto-refresh (except when the sidebar panel is not visible).
- *
  * Usage example:
  *
  * AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
  *                         onPlayers);
  * function onPlayers() {
  *   for (let player of AnimationsController.animationPlayers) {
  *     // do something with player
  *   }
@@ -175,17 +170,17 @@ var AnimationsController = {
       this.animationsFront = null;
     }
 
     this.destroyed.resolve();
   }),
 
   startListeners: function() {
     // Re-create the list of players when a new node is selected, except if the
-    // sidebar isn't visible. And set the players to auto-refresh when needed.
+    // sidebar isn't visible.
     gInspector.selection.on("new-node-front", this.onNewNodeFront);
     gInspector.sidebar.on("select", this.onPanelVisibilityChange);
     gToolbox.on("select", this.onPanelVisibilityChange);
   },
 
   stopListeners: function() {
     gInspector.selection.off("new-node-front", this.onNewNodeFront);
     gInspector.sidebar.off("select", this.onPanelVisibilityChange);
@@ -199,19 +194,16 @@ var AnimationsController = {
     return gToolbox.currentToolId === "inspector" &&
            gInspector.sidebar &&
            gInspector.sidebar.getCurrentTabID() == "animationinspector";
   },
 
   onPanelVisibilityChange: Task.async(function*() {
     if (this.isPanelVisible()) {
       this.onNewNodeFront();
-      this.startAllAutoRefresh();
-    } else {
-      this.stopAllAutoRefresh();
     }
   }),
 
   onNewNodeFront: Task.async(function*() {
     // Ignore if the panel isn't visible or the node selection hasn't changed.
     if (!this.isPanelVisible() ||
         this.nodeFront === gInspector.selection.nodeFront) {
       return;
@@ -241,29 +233,52 @@ var AnimationsController = {
     if (!this.traits.hasToggleAll) {
       return promise.resolve();
     }
 
     return this.animationsFront.toggleAll().catch(e => console.error(e));
   },
 
   /**
+   * Similar to toggleAll except that it only plays/pauses the currently known
+   * animations (those listed in this.animationPlayers).
+   * @param {Boolean} shouldPause True if the animations should be paused, false
+   * if they should be played.
+   * @return {Promise} Resolves when the playState has been changed.
+   */
+  toggleCurrentAnimations: Task.async(function*(shouldPause) {
+    if (this.traits.hasToggleSeveral) {
+      yield this.animationsFront.toggleSeveral(this.animationPlayers,
+                                               shouldPause);
+    } else {
+      // Fall back to pausing/playing the players one by one, which is bound to
+      // introduce some de-synchronization.
+      for (let player of this.animationPlayers) {
+        if (shouldPause) {
+          yield player.pause();
+        } else {
+          yield player.play();
+        }
+      }
+    }
+  }),
+
+  /**
    * Set all known animations' currentTimes to the provided time.
-   * Note that depending on the server's capabilities, this might resolve in
-   * either one packet, or as many packets as there are animations. In the
-   * latter case, some time deltas might be introduced.
    * @param {Number} time.
    * @param {Boolean} shouldPause Should the animations be paused too.
    * @return {Promise} Resolves when the current time has been set.
    */
   setCurrentTimeAll: Task.async(function*(time, shouldPause) {
     if (this.traits.hasSetCurrentTimes) {
       yield this.animationsFront.setCurrentTimes(this.animationPlayers, time,
                                                  shouldPause);
     } else {
+      // Fall back to pausing and setting the current time on each player, one
+      // by one, which is bound to introduce some de-synchronization.
       for (let animation of this.animationPlayers) {
         if (shouldPause) {
           yield animation.pause();
         }
         yield animation.setCurrentTime(time);
       }
     }
   }),
@@ -274,41 +289,34 @@ var AnimationsController = {
   // called again.
   animationPlayers: [],
 
   refreshAnimationPlayers: Task.async(function*(nodeFront) {
     yield this.destroyAnimationPlayers();
 
     this.animationPlayers = yield this.animationsFront
                                       .getAnimationPlayersForNode(nodeFront);
-    this.startAllAutoRefresh();
 
     // Start listening for animation mutations only after the first method call
     // otherwise events won't be sent.
     if (!this.isListeningToMutations && this.traits.hasMutationEvents) {
       this.animationsFront.on("mutations", this.onAnimationMutations);
       this.isListeningToMutations = true;
     }
   }),
 
   onAnimationMutations: Task.async(function*(changes) {
     // Insert new players into this.animationPlayers when new animations are
     // added.
     for (let {type, player} of changes) {
       if (type === "added") {
         this.animationPlayers.push(player);
-        if (!this.traits.isNewUI) {
-          player.startAutoRefresh();
-        }
       }
 
       if (type === "removed") {
-        if (!this.traits.isNewUI) {
-          player.stopAutoRefresh();
-        }
         yield player.release();
         let index = this.animationPlayers.indexOf(player);
         this.animationPlayers.splice(index, 1);
       }
     }
 
     // Let the UI know the list has been updated.
     this.emit(this.PLAYERS_UPDATED_EVENT, this.animationPlayers);
@@ -328,44 +336,24 @@ var AnimationsController = {
       if (!state.documentCurrentTime) {
         return false;
       }
       time = Math.max(time, state.documentCurrentTime);
     }
     return time;
   },
 
-  startAllAutoRefresh: function() {
-    if (this.traits.isNewUI) {
-      return;
-    }
-
-    for (let front of this.animationPlayers) {
-      front.startAutoRefresh();
-    }
-  },
-
-  stopAllAutoRefresh: function() {
-    if (this.traits.isNewUI) {
-      return;
-    }
-
-    for (let front of this.animationPlayers) {
-      front.stopAutoRefresh();
-    }
-  },
-
   destroyAnimationPlayers: Task.async(function*() {
     // Let the server know that we're not interested in receiving updates about
     // players for the current node. We're either being destroyed or a new node
     // has been selected.
     if (this.traits.hasMutationEvents) {
       yield this.animationsFront.stopAnimationPlayerUpdates();
     }
-    this.stopAllAutoRefresh();
+
     for (let front of this.animationPlayers) {
       yield front.release();
     }
     this.animationPlayers = [];
   })
 };
 
 EventEmitter.decorate(AnimationsController);
--- a/browser/devtools/animationinspector/animation-inspector.xhtml
+++ b/browser/devtools/animationinspector/animation-inspector.xhtml
@@ -9,20 +9,23 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>&title;</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://browser/skin/devtools/animationinspector.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
   </head>
-  <body class="theme-sidebar devtools-monospace" role="application">
-    <div id="toolbar" class="theme-toolbar">
+  <body class="theme-sidebar devtools-monospace" role="application" empty="true">
+    <div id="global-toolbar" class="theme-toolbar">
       <span class="label">&allAnimations;</span>
-      <button id="toggle-all" standalone="true" class="devtools-button"></button>
+      <button id="toggle-all" standalone="true" class="devtools-button pause-button"></button>
+    </div>
+    <div id="timeline-toolbar" class="theme-toolbar">
+      <button id="pause-resume-timeline" standalone="true" class="devtools-button pause-button paused"></button>
     </div>
     <div id="players"></div>
     <div id="error-message">
       <p>&invalidElement;</p>
       <p>&selectElement;</p>
       <button id="element-picker" standalone="true" class="devtools-button"></button>
     </div>
     <script type="application/javascript;version=1.8" src="animation-controller.js"></script>
--- a/browser/devtools/animationinspector/animation-panel.js
+++ b/browser/devtools/animationinspector/animation-panel.js
@@ -3,23 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* globals AnimationsController, document, performance, promise,
    gToolbox, gInspector, requestAnimationFrame, cancelAnimationFrame, L10N */
 
 "use strict";
 
-const {createNode} = require("devtools/animationinspector/utils");
-const {
-  PlayerMetaDataHeader,
-  PlaybackRateSelector,
-  AnimationTargetNode,
-  AnimationsTimeline
-} = require("devtools/animationinspector/components");
+const {AnimationsTimeline} = require("devtools/animationinspector/components");
 
 /**
  * The main animations panel UI.
  */
 var AnimationsPanel = {
   UI_UPDATED_EVENT: "ui-updated",
   PANEL_INITIALIZED: "panel-initialized",
 
@@ -34,36 +28,35 @@ var AnimationsPanel = {
       return;
     }
     this.initialized = promise.defer();
 
     this.playersEl = document.querySelector("#players");
     this.errorMessageEl = document.querySelector("#error-message");
     this.pickerButtonEl = document.querySelector("#element-picker");
     this.toggleAllButtonEl = document.querySelector("#toggle-all");
+    this.playTimelineButtonEl = document.querySelector("#pause-resume-timeline");
 
     // If the server doesn't support toggling all animations at once, hide the
-    // whole bottom toolbar.
+    // whole global toolbar.
     if (!AnimationsController.traits.hasToggleAll) {
-      document.querySelector("#toolbar").style.display = "none";
+      document.querySelector("#global-toolbar").style.display = "none";
     }
 
+    // Binding functions that need to be called in scope.
+    for (let functionName of ["onPickerStarted", "onPickerStopped",
+      "refreshAnimations", "toggleAll", "onTabNavigated",
+      "onTimelineDataChanged", "playPauseTimeline"]) {
+      this[functionName] = this[functionName].bind(this);
+    }
     let hUtils = gToolbox.highlighterUtils;
     this.togglePicker = hUtils.togglePicker.bind(hUtils);
-    this.onPickerStarted = this.onPickerStarted.bind(this);
-    this.onPickerStopped = this.onPickerStopped.bind(this);
-    this.refreshAnimations = this.refreshAnimations.bind(this);
-    this.toggleAll = this.toggleAll.bind(this);
-    this.onTabNavigated = this.onTabNavigated.bind(this);
-    this.onTimelineTimeChanged = this.onTimelineTimeChanged.bind(this);
 
-    if (AnimationsController.traits.isNewUI) {
-      this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
-      this.animationsTimelineComponent.init(this.playersEl);
-    }
+    this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
+    this.animationsTimelineComponent.init(this.playersEl);
 
     this.startListeners();
 
     yield this.refreshAnimations();
 
     this.initialized.resolve();
 
     this.emit(this.PANEL_INITIALIZED);
@@ -77,149 +70,144 @@ var AnimationsPanel = {
     if (this.destroyed) {
       yield this.destroyed.promise;
       return;
     }
     this.destroyed = promise.defer();
 
     this.stopListeners();
 
-    if (this.animationsTimelineComponent) {
-      this.animationsTimelineComponent.destroy();
-      this.animationsTimelineComponent = null;
-    }
+    this.animationsTimelineComponent.destroy();
+    this.animationsTimelineComponent = null;
+
     yield this.destroyPlayerWidgets();
 
     this.playersEl = this.errorMessageEl = null;
     this.toggleAllButtonEl = this.pickerButtonEl = null;
+    this.playTimelineButtonEl = null;
 
     this.destroyed.resolve();
   }),
 
   startListeners: function() {
     AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimations);
 
-    this.pickerButtonEl.addEventListener("click", this.togglePicker, false);
+    this.pickerButtonEl.addEventListener("click", this.togglePicker);
     gToolbox.on("picker-started", this.onPickerStarted);
     gToolbox.on("picker-stopped", this.onPickerStopped);
 
-    this.toggleAllButtonEl.addEventListener("click", this.toggleAll, false);
+    this.toggleAllButtonEl.addEventListener("click", this.toggleAll);
+    this.playTimelineButtonEl.addEventListener("click", this.playPauseTimeline);
     gToolbox.target.on("navigate", this.onTabNavigated);
 
-    if (this.animationsTimelineComponent) {
-      this.animationsTimelineComponent.on("current-time-changed",
-        this.onTimelineTimeChanged);
-    }
+    this.animationsTimelineComponent.on("timeline-data-changed",
+      this.onTimelineDataChanged);
   },
 
   stopListeners: function() {
     AnimationsController.off(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimations);
 
-    this.pickerButtonEl.removeEventListener("click", this.togglePicker, false);
+    this.pickerButtonEl.removeEventListener("click", this.togglePicker);
     gToolbox.off("picker-started", this.onPickerStarted);
     gToolbox.off("picker-stopped", this.onPickerStopped);
 
-    this.toggleAllButtonEl.removeEventListener("click", this.toggleAll, false);
+    this.toggleAllButtonEl.removeEventListener("click", this.toggleAll);
+    this.playTimelineButtonEl.removeEventListener("click", this.playPauseTimeline);
     gToolbox.target.off("navigate", this.onTabNavigated);
 
-    if (this.animationsTimelineComponent) {
-      this.animationsTimelineComponent.off("current-time-changed",
-        this.onTimelineTimeChanged);
-    }
+    this.animationsTimelineComponent.off("timeline-data-changed",
+      this.onTimelineDataChanged);
   },
 
-  displayErrorMessage: function() {
-    this.errorMessageEl.style.display = "block";
-    this.playersEl.style.display = "none";
-  },
-
-  hideErrorMessage: function() {
-    this.errorMessageEl.style.display = "none";
-    this.playersEl.style.display = "block";
+  togglePlayers: function(isVisible) {
+    if (isVisible) {
+      document.body.removeAttribute("empty");
+      document.body.setAttribute("timeline", "true");
+    } else {
+      document.body.setAttribute("empty", "true");
+      document.body.removeAttribute("timeline");
+    }
   },
 
   onPickerStarted: function() {
     this.pickerButtonEl.setAttribute("checked", "true");
   },
 
   onPickerStopped: function() {
     this.pickerButtonEl.removeAttribute("checked");
   },
 
   toggleAll: Task.async(function*() {
-    let btnClass = this.toggleAllButtonEl.classList;
+    this.toggleAllButtonEl.classList.toggle("paused");
+    yield AnimationsController.toggleAll();
+  }),
 
-    if (!AnimationsController.traits.isNewUI) {
-      // Toggling all animations is async and it may be some time before each of
-      // the current players get their states updated, so toggle locally too, to
-      // avoid the timelines from jumping back and forth.
-      if (this.playerWidgets) {
-        let currentWidgetStateChange = [];
-        for (let widget of this.playerWidgets) {
-          currentWidgetStateChange.push(btnClass.contains("paused")
-            ? widget.play() : widget.pause());
-        }
-        yield promise.all(currentWidgetStateChange)
-                     .catch(error => console.error(error));
-      }
+  /**
+   * Depending on the state of the timeline either pause or play the animations
+   * displayed in it.
+   * If the animations are finished, this will play them from the start again.
+   * If the animations are playing, this will pause them.
+   * If the animations are paused, this will resume them.
+   */
+  playPauseTimeline: Task.async(function*() {
+    yield AnimationsController.toggleCurrentAnimations(this.timelineData.isMoving);
+
+    // Now that the playState have been changed make sure the player (the
+    // fronts) are up to date, and then refresh the UI.
+    for (let player of AnimationsController.animationPlayers) {
+      yield player.refreshState();
     }
-
-    btnClass.toggle("paused");
-    yield AnimationsController.toggleAll();
+    yield this.refreshAnimations();
   }),
 
   onTabNavigated: function() {
     this.toggleAllButtonEl.classList.remove("paused");
   },
 
-  onTimelineTimeChanged: function(e, time) {
-    AnimationsController.setCurrentTimeAll(time, true)
-                        .catch(error => console.error(error));
+  onTimelineDataChanged: function(e, data) {
+    this.timelineData = data;
+    let {isPaused, isMoving, time} = data;
+
+    this.playTimelineButtonEl.classList.toggle("paused", !isMoving);
+
+    // Pause all animations and set their currentTimes (but only do this after
+    // the previous currentTime setting is done, as this gets called many times
+    // when users drag the scrubber with the mouse, and we want the server-side
+    // requests to be sequenced).
+    if (isPaused && !this.setCurrentTimeAllPromise) {
+      this.setCurrentTimeAllPromise =
+        AnimationsController.setCurrentTimeAll(time, true)
+                            .catch(error => console.error(error))
+                            .then(() => this.setCurrentTimeAllPromise = null);
+    }
   },
 
   refreshAnimations: Task.async(function*() {
     let done = gInspector.updating("animationspanel");
 
     // Empty the whole panel first.
-    this.hideErrorMessage();
+    this.togglePlayers(true);
     yield this.destroyPlayerWidgets();
 
     // Re-render the timeline component.
-    if (this.animationsTimelineComponent) {
-      this.animationsTimelineComponent.render(
-        AnimationsController.animationPlayers,
-        AnimationsController.documentCurrentTime);
-    }
+    this.animationsTimelineComponent.render(
+      AnimationsController.animationPlayers,
+      AnimationsController.documentCurrentTime);
 
     // If there are no players to show, show the error message instead and
     // return.
     if (!AnimationsController.animationPlayers.length) {
-      this.displayErrorMessage();
+      this.togglePlayers(false);
       this.emit(this.UI_UPDATED_EVENT);
       done();
       return;
     }
 
-    // Otherwise, create player widgets (only when isNewUI is false, the
-    // timeline has already been re-rendered).
-    if (!AnimationsController.traits.isNewUI) {
-      this.playerWidgets = [];
-      let initPromises = [];
-
-      for (let player of AnimationsController.animationPlayers) {
-        let widget = new PlayerWidget(player, this.playersEl);
-        initPromises.push(widget.initialize());
-        this.playerWidgets.push(widget);
-      }
-
-      yield initPromises;
-    }
-
     this.emit(this.UI_UPDATED_EVENT);
     done();
   }),
 
   destroyPlayerWidgets: Task.async(function*() {
     if (!this.playerWidgets) {
       return;
     }
@@ -227,412 +215,8 @@ var AnimationsPanel = {
     let destroyers = this.playerWidgets.map(widget => widget.destroy());
     yield promise.all(destroyers);
     this.playerWidgets = null;
     this.playersEl.innerHTML = "";
   })
 };
 
 EventEmitter.decorate(AnimationsPanel);
-
-/**
- * An AnimationPlayer UI widget
- */
-function PlayerWidget(player, containerEl) {
-  EventEmitter.decorate(this);
-
-  this.player = player;
-  this.containerEl = containerEl;
-
-  this.onStateChanged = this.onStateChanged.bind(this);
-  this.onPlayPauseBtnClick = this.onPlayPauseBtnClick.bind(this);
-  this.onRewindBtnClick = this.onRewindBtnClick.bind(this);
-  this.onFastForwardBtnClick = this.onFastForwardBtnClick.bind(this);
-  this.onCurrentTimeChanged = this.onCurrentTimeChanged.bind(this);
-  this.onPlaybackRateChanged = this.onPlaybackRateChanged.bind(this);
-
-  this.metaDataComponent = new PlayerMetaDataHeader();
-  if (AnimationsController.traits.hasSetPlaybackRate) {
-    this.rateComponent = new PlaybackRateSelector();
-  }
-  if (AnimationsController.traits.hasTargetNode) {
-    this.targetNodeComponent = new AnimationTargetNode(gInspector);
-  }
-}
-
-PlayerWidget.prototype = {
-  initialize: Task.async(function*() {
-    if (this.initialized) {
-      return;
-    }
-    this.initialized = true;
-
-    this.createMarkup();
-    this.startListeners();
-  }),
-
-  destroy: Task.async(function*() {
-    if (this.destroyed) {
-      return;
-    }
-    this.destroyed = true;
-
-    this.stopTimelineAnimation();
-    this.stopListeners();
-    this.metaDataComponent.destroy();
-    if (this.rateComponent) {
-      this.rateComponent.destroy();
-    }
-    if (this.targetNodeComponent) {
-      this.targetNodeComponent.destroy();
-    }
-
-    this.el.remove();
-    this.playPauseBtnEl = this.rewindBtnEl = this.fastForwardBtnEl = null;
-    this.currentTimeEl = this.timeDisplayEl = null;
-    this.containerEl = this.el = this.player = null;
-  }),
-
-  startListeners: function() {
-    this.player.on(this.player.AUTO_REFRESH_EVENT, this.onStateChanged);
-    this.playPauseBtnEl.addEventListener("click", this.onPlayPauseBtnClick);
-    if (AnimationsController.traits.hasSetCurrentTime) {
-      this.rewindBtnEl.addEventListener("click", this.onRewindBtnClick);
-      this.fastForwardBtnEl.addEventListener("click", this.onFastForwardBtnClick);
-      this.currentTimeEl.addEventListener("input", this.onCurrentTimeChanged);
-    }
-    if (this.rateComponent) {
-      this.rateComponent.on("rate-changed", this.onPlaybackRateChanged);
-    }
-  },
-
-  stopListeners: function() {
-    this.player.off(this.player.AUTO_REFRESH_EVENT, this.onStateChanged);
-    this.playPauseBtnEl.removeEventListener("click", this.onPlayPauseBtnClick);
-    if (AnimationsController.traits.hasSetCurrentTime) {
-      this.rewindBtnEl.removeEventListener("click", this.onRewindBtnClick);
-      this.fastForwardBtnEl.removeEventListener("click", this.onFastForwardBtnClick);
-      this.currentTimeEl.removeEventListener("input", this.onCurrentTimeChanged);
-    }
-    if (this.rateComponent) {
-      this.rateComponent.off("rate-changed", this.onPlaybackRateChanged);
-    }
-  },
-
-  createMarkup: function() {
-    let state = this.player.state;
-
-    this.el = createNode({
-      parent: this.containerEl,
-      attributes: {
-        "class": "player-widget " + state.playState
-      }
-    });
-
-    if (this.targetNodeComponent) {
-      this.targetNodeComponent.init(this.el);
-      this.targetNodeComponent.render(this.player);
-    }
-
-    this.metaDataComponent.init(this.el);
-    this.metaDataComponent.render(state);
-
-    // Timeline widget.
-    let timelineEl = createNode({
-      parent: this.el,
-      attributes: {
-        "class": "timeline"
-      }
-    });
-
-    // Playback control buttons container.
-    let playbackControlsEl = createNode({
-      parent: timelineEl,
-      attributes: {
-        "class": "playback-controls"
-      }
-    });
-
-    // Control buttons.
-    this.playPauseBtnEl = createNode({
-      parent: playbackControlsEl,
-      nodeType: "button",
-      attributes: {
-        "class": "toggle devtools-button"
-      }
-    });
-
-    if (AnimationsController.traits.hasSetCurrentTime) {
-      this.rewindBtnEl = createNode({
-        parent: playbackControlsEl,
-        nodeType: "button",
-        attributes: {
-          "class": "rw devtools-button"
-        }
-      });
-
-      this.fastForwardBtnEl = createNode({
-        parent: playbackControlsEl,
-        nodeType: "button",
-        attributes: {
-          "class": "ff devtools-button"
-        }
-      });
-    }
-
-    if (this.rateComponent) {
-      this.rateComponent.init(playbackControlsEl);
-      this.rateComponent.render(state);
-    }
-
-    // Sliders container.
-    let slidersContainerEl = createNode({
-      parent: timelineEl,
-      attributes: {
-        "class": "sliders-container",
-      }
-    });
-
-    let max = state.duration;
-    if (state.iterationCount) {
-      // If there's a finite nb of iterations.
-      max = state.iterationCount * state.duration;
-    }
-
-    // For now, keyframes aren't exposed by the actor. So the only range <input>
-    // displayed in the container is the currentTime. When keyframes are
-    // available, one input per keyframe can be added here.
-    this.currentTimeEl = createNode({
-      nodeType: "input",
-      parent: slidersContainerEl,
-      attributes: {
-        "type": "range",
-        "class": "current-time",
-        "min": "0",
-        "max": max,
-        "step": "10",
-        "value": "0"
-      }
-    });
-
-    if (!AnimationsController.traits.hasSetCurrentTime) {
-      this.currentTimeEl.setAttribute("disabled", "true");
-    }
-
-    // Time display
-    this.timeDisplayEl = createNode({
-      parent: timelineEl,
-      attributes: {
-        "class": "time-display"
-      }
-    });
-
-    // Show the initial time.
-    this.displayTime(state.currentTime);
-  },
-
-  /**
-   * Executed when the playPause button is clicked.
-   * Note that tests may want to call this callback directly rather than
-   * simulating a click on the button since it returns the promise returned by
-   * play and paused.
-   * @return {Promise}
-   */
-  onPlayPauseBtnClick: function() {
-    if (this.player.state.playState === "running") {
-      return this.pause();
-    }
-    return this.play();
-  },
-
-  onRewindBtnClick: function() {
-    this.setCurrentTime(0, true);
-  },
-
-  onFastForwardBtnClick: function() {
-    let state = this.player.state;
-
-    let time = state.duration;
-    if (state.iterationCount) {
-      time = state.iterationCount * state.duration;
-    }
-    this.setCurrentTime(time, true);
-  },
-
-  /**
-   * Executed when the current-time range input is changed.
-   */
-  onCurrentTimeChanged: function(e) {
-    let time = e.target.value;
-    this.setCurrentTime(parseFloat(time), true);
-  },
-
-  /**
-   * Executed when the playback rate dropdown value changes in the playbackrate
-   * component.
-   */
-  onPlaybackRateChanged: function(e, rate) {
-    this.setPlaybackRate(rate);
-  },
-
-  /**
-   * Whenever a player state update is received.
-   */
-  onStateChanged: function() {
-    let state = this.player.state;
-
-    this.updateWidgetState(state);
-    this.metaDataComponent.render(state);
-    if (this.rateComponent) {
-      this.rateComponent.render(state);
-    }
-
-    switch (state.playState) {
-      case "finished":
-        this.stopTimelineAnimation();
-        this.displayTime(this.player.state.currentTime);
-        break;
-      case "running":
-        this.startTimelineAnimation();
-        break;
-      case "paused":
-        this.stopTimelineAnimation();
-        this.displayTime(this.player.state.currentTime);
-        break;
-      case "idle":
-        this.stopTimelineAnimation();
-        this.displayTime(0);
-        break;
-    }
-  },
-
-  /**
-   * Set the current time of the animation.
-   * @param {Number} time.
-   * @param {Boolean} shouldPause Should the player be paused too.
-   * @return {Promise} Resolves when the current time has been set.
-   */
-  setCurrentTime: Task.async(function*(time, shouldPause) {
-    if (!AnimationsController.traits.hasSetCurrentTime) {
-      throw new Error("This server version doesn't support setting " +
-                      "animations' currentTime");
-    }
-
-    if (shouldPause) {
-      this.stopTimelineAnimation();
-      yield this.pause();
-    }
-
-    if (this.player.state.delay) {
-      time += this.player.state.delay;
-    }
-
-    // Set the time locally first so it feels instant, even if the request to
-    // actually set the time is async.
-    this.displayTime(time);
-
-    yield this.player.setCurrentTime(time);
-  }),
-
-  /**
-   * Set the playback rate of the animation.
-   * @param {Number} rate.
-   * @return {Promise} Resolves when the rate has been set.
-   */
-  setPlaybackRate: function(rate) {
-    if (!AnimationsController.traits.hasSetPlaybackRate) {
-      throw new Error("This server version doesn't support setting " +
-                      "animations' playbackRate");
-    }
-
-    return this.player.setPlaybackRate(rate);
-  },
-
-  /**
-   * Pause the animation player via this widget.
-   * @return {Promise} Resolves when the player is paused, the button is
-   * switched to the right state, and the timeline animation is stopped.
-   */
-  pause: function() {
-    // Switch to the right className on the element right away to avoid waiting
-    // for the next state update to change the playPause icon.
-    this.updateWidgetState({playState: "paused"});
-    this.stopTimelineAnimation();
-    return this.player.pause();
-  },
-
-  /**
-   * Play the animation player via this widget.
-   * @return {Promise} Resolves when the player is playing, the button is
-   * switched to the right state, and the timeline animation is started.
-   */
-  play: function() {
-    // Switch to the right className on the element right away to avoid waiting
-    // for the next state update to change the playPause icon.
-    this.updateWidgetState({playState: "running"});
-    this.startTimelineAnimation();
-    return this.player.play();
-  },
-
-  updateWidgetState: function({playState}) {
-    this.el.className = "player-widget " + playState;
-  },
-
-  /**
-   * Make the timeline progress smoothly, even though the currentTime is only
-   * updated at some intervals. This uses a local animation loop.
-   */
-  startTimelineAnimation: function() {
-    this.stopTimelineAnimation();
-
-    let state = this.player.state;
-
-    let start = performance.now();
-    let loop = () => {
-      this.rafID = requestAnimationFrame(loop);
-      let delta = (performance.now() - start) * state.playbackRate;
-      let now = state.currentTime + delta;
-      this.displayTime(now);
-    };
-
-    loop();
-  },
-
-  /**
-   * Display the time in the timeDisplayEl and in the currentTimeEl slider.
-   */
-  displayTime: function(time) {
-    let state = this.player.state;
-
-    // If the animation is delayed, don't start displaying the time until the
-    // delay has passed.
-    if (state.delay) {
-      time = Math.max(0, time - state.delay);
-    }
-
-    // For finite animations, make sure the displayed time does not go beyond
-    // the animation total duration (this may happen due to the local
-    // requestAnimationFrame loop).
-    if (state.iterationCount) {
-      time = Math.min(time, state.iterationCount * state.duration);
-    }
-
-    // Set the time label value.
-    this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
-      L10N.numberWithDecimals(time / 1000, 2));
-
-    // Set the timeline slider value.
-    if (!state.iterationCount && time !== state.duration) {
-      time = time % state.duration;
-    }
-    this.currentTimeEl.value = time;
-  },
-
-  /**
-   * Stop the animation loop that makes the timeline progress.
-   */
-  stopTimelineAnimation: function() {
-    if (this.rafID) {
-      cancelAnimationFrame(this.rafID);
-      this.rafID = null;
-    }
-  }
-};
--- a/browser/devtools/animationinspector/components.js
+++ b/browser/devtools/animationinspector/components.js
@@ -31,278 +31,16 @@ const {
 
 const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const MILLIS_TIME_FORMAT_MAX_DURATION = 4000;
 // The minimum spacing between 2 time graduation headers in the timeline (px).
 const TIME_GRADUATION_MIN_SPACING = 40;
 
 /**
- * UI component responsible for displaying and updating the player meta-data:
- * name, duration, iterations, delay.
- * The parent UI component for this should drive its updates by calling
- * render(state) whenever it wants the component to update.
- */
-function PlayerMetaDataHeader() {
-  // Store the various state pieces we need to only refresh the UI when things
-  // change.
-  this.state = {};
-}
-
-exports.PlayerMetaDataHeader = PlayerMetaDataHeader;
-
-PlayerMetaDataHeader.prototype = {
-  init: function(containerEl) {
-    // The main title element.
-    this.el = createNode({
-      parent: containerEl,
-      attributes: {
-        "class": "animation-title"
-      }
-    });
-
-    // Animation name.
-    this.nameLabel = createNode({
-      parent: this.el,
-      nodeType: "span"
-    });
-
-    this.nameValue = createNode({
-      parent: this.el,
-      nodeType: "strong",
-      attributes: {
-        "style": "display:none;"
-      }
-    });
-
-    // Animation duration, delay and iteration container.
-    let metaData = createNode({
-      parent: this.el,
-      nodeType: "span",
-      attributes: {
-        "class": "meta-data"
-      }
-    });
-
-    // Animation is running on compositor
-    this.compositorIcon = createNode({
-      parent: metaData,
-      nodeType: "span",
-      attributes: {
-        "class": "compositor-icon",
-        "title": L10N.getStr("player.runningOnCompositorTooltip")
-      }
-    });
-
-    // Animation duration.
-    this.durationLabel = createNode({
-      parent: metaData,
-      nodeType: "span",
-      textContent: L10N.getStr("player.animationDurationLabel")
-    });
-
-    this.durationValue = createNode({
-      parent: metaData,
-      nodeType: "strong"
-    });
-
-    // Animation delay (hidden by default since there may not be a delay).
-    this.delayLabel = createNode({
-      parent: metaData,
-      nodeType: "span",
-      attributes: {
-        "style": "display:none;"
-      },
-      textContent: L10N.getStr("player.animationDelayLabel")
-    });
-
-    this.delayValue = createNode({
-      parent: metaData,
-      nodeType: "strong"
-    });
-
-    // Animation iteration count (also hidden by default since we don't display
-    // single iterations).
-    this.iterationLabel = createNode({
-      parent: metaData,
-      nodeType: "span",
-      attributes: {
-        "style": "display:none;"
-      },
-      textContent: L10N.getStr("player.animationIterationCountLabel")
-    });
-
-    this.iterationValue = createNode({
-      parent: metaData,
-      nodeType: "strong",
-      attributes: {
-        "style": "display:none;"
-      }
-    });
-  },
-
-  destroy: function() {
-    this.state = null;
-    this.el.remove();
-    this.el = null;
-    this.nameLabel = this.nameValue = null;
-    this.durationLabel = this.durationValue = null;
-    this.delayLabel = this.delayValue = null;
-    this.iterationLabel = this.iterationValue = null;
-    this.compositorIcon = null;
-  },
-
-  render: function(state) {
-    // Update the name if needed.
-    if (state.name !== this.state.name) {
-      if (state.name) {
-        // Animations (and transitions since bug 1122414) have names.
-        this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
-        this.nameValue.style.display = "inline";
-        this.nameValue.textContent = state.name;
-      } else {
-        // With older actors, Css transitions don't have names.
-        this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
-        this.nameValue.style.display = "none";
-      }
-    }
-
-    // update the duration value if needed.
-    if (state.duration !== this.state.duration) {
-      this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
-        L10N.numberWithDecimals(state.duration / 1000, 2));
-    }
-
-    // Update the delay if needed.
-    if (state.delay !== this.state.delay) {
-      if (state.delay) {
-        this.delayLabel.style.display = "inline";
-        this.delayValue.style.display = "inline";
-        this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
-          L10N.numberWithDecimals(state.delay / 1000, 2));
-      } else {
-        // Hide the delay elements if there is no delay defined.
-        this.delayLabel.style.display = "none";
-        this.delayValue.style.display = "none";
-      }
-    }
-
-    // Update the iterationCount if needed.
-    if (state.iterationCount !== this.state.iterationCount) {
-      if (state.iterationCount !== 1) {
-        this.iterationLabel.style.display = "inline";
-        this.iterationValue.style.display = "inline";
-        let count = state.iterationCount ||
-                    L10N.getStr("player.infiniteIterationCount");
-        this.iterationValue.innerHTML = count;
-      } else {
-        // Hide the iteration elements if iteration is 1.
-        this.iterationLabel.style.display = "none";
-        this.iterationValue.style.display = "none";
-      }
-    }
-
-    // Show the Running on compositor icon if needed.
-    if (state.isRunningOnCompositor !== this.state.isRunningOnCompositor) {
-      if (state.isRunningOnCompositor) {
-        this.compositorIcon.style.display = "inline";
-      } else {
-        // Hide the compositor icon
-        this.compositorIcon.style.display = "none";
-      }
-    }
-
-    this.state = state;
-  }
-};
-
-/**
- * UI component responsible for displaying the playback rate drop-down in each
- * player widget, updating it when the state changes, and emitting events when
- * the user selects a new value.
- * The parent UI component for this should drive its updates by calling
- * render(state) whenever it wants the component to update.
- */
-function PlaybackRateSelector() {
-  this.currentRate = null;
-  this.onSelectionChanged = this.onSelectionChanged.bind(this);
-  EventEmitter.decorate(this);
-}
-
-exports.PlaybackRateSelector = PlaybackRateSelector;
-
-PlaybackRateSelector.prototype = {
-  PRESETS: [.1, .5, 1, 2, 5, 10],
-
-  init: function(containerEl) {
-    // This component is simple enough that we can re-create the markup every
-    // time it's rendered. So here we only store the parentEl.
-    this.parentEl = containerEl;
-  },
-
-  destroy: function() {
-    this.removeSelect();
-    this.parentEl = this.el = null;
-  },
-
-  removeSelect: function() {
-    if (this.el) {
-      this.el.removeEventListener("change", this.onSelectionChanged);
-      this.el.remove();
-    }
-  },
-
-  /**
-   * Get the ordered list of presets, including the current playbackRate if
-   * different from the existing presets.
-   */
-  getCurrentPresets: function({playbackRate}) {
-    return [...new Set([...this.PRESETS, playbackRate])].sort((a, b) => a > b);
-  },
-
-  render: function(state) {
-    if (state.playbackRate === this.currentRate) {
-      return;
-    }
-
-    this.removeSelect();
-
-    this.el = createNode({
-      parent: this.parentEl,
-      nodeType: "select",
-      attributes: {
-        "class": "rate devtools-button"
-      }
-    });
-
-    for (let preset of this.getCurrentPresets(state)) {
-      let option = createNode({
-        parent: this.el,
-        nodeType: "option",
-        attributes: {
-          value: preset,
-        },
-        textContent: L10N.getFormatStr("player.playbackRateLabel", preset)
-      });
-      if (preset === state.playbackRate) {
-        option.setAttribute("selected", "");
-      }
-    }
-
-    this.el.addEventListener("change", this.onSelectionChanged);
-
-    this.currentRate = state.playbackRate;
-  },
-
-  onSelectionChanged: function() {
-    this.emit("rate-changed", parseFloat(this.el.value));
-  }
-};
-
-/**
  * UI component responsible for displaying a preview of the target dom node of
  * a given animation.
  * @param {InspectorPanel} inspector Requires a reference to the inspector-panel
  * to highlight and select the node, as well as refresh it when there are
  * mutations.
  * @param {Object} options Supported properties are:
  * - compact {Boolean} Defaults to false. If true, nodes will be previewed like
  *   tag#id.class instead of <tag id="id" class="class">
@@ -560,28 +298,31 @@ var TimeScale = {
   minStartTime: Infinity,
   maxEndTime: 0,
 
   /**
    * Add a new animation to time scale.
    * @param {Object} state A PlayerFront.state object.
    */
   addAnimation: function(state) {
-    let {startTime, delay, duration, iterationCount, playbackRate} = state;
+    let {previousStartTime, delay, duration,
+         iterationCount, playbackRate} = state;
 
     // Negative-delayed animations have their startTimes set such that we would
     // be displaying the delay outside the time window if we didn't take it into
     // account here.
     let relevantDelay = delay < 0 ? delay / playbackRate : 0;
+    previousStartTime = previousStartTime || 0;
 
-    this.minStartTime = Math.min(this.minStartTime, startTime + relevantDelay);
+    this.minStartTime = Math.min(this.minStartTime,
+                                 previousStartTime + relevantDelay);
     let length = (delay / playbackRate) +
                  ((duration / playbackRate) *
                   (!iterationCount ? 1 : iterationCount));
-    this.maxEndTime = Math.max(this.maxEndTime, startTime + length);
+    this.maxEndTime = Math.max(this.maxEndTime, previousStartTime + length);
   },
 
   /**
    * Reset the current time scale.
    */
   reset: function() {
     this.minStartTime = Infinity;
     this.maxEndTime = 0;
@@ -656,18 +397,18 @@ exports.TimeScale = TimeScale;
  * UI component responsible for displaying a timeline for animations.
  * The timeline is essentially a graph with time along the x axis and animations
  * along the y axis.
  * The time is represented with a graduation header at the top and a current
  * time play head.
  * Animations are organized by lines, with a left margin containing the preview
  * of the target DOM element the animation applies to.
  * The current time play head can be moved by clicking/dragging in the header.
- * when this happens, the component emits "current-time-changed" events with the
- * new time.
+ * when this happens, the component emits "current-data-changed" events with the
+ * new time and state of the timeline.
  *
  * @param {InspectorPanel} inspector.
  */
 function AnimationsTimeline(inspector) {
   this.animations = [];
   this.targetNodes = [];
   this.inspector = inspector;
 
@@ -788,17 +529,22 @@ AnimationsTimeline.prototype = {
     if (offset < 0) {
       offset = 0;
     }
 
     this.scrubberEl.style.left = offset + "px";
 
     let time = TimeScale.distanceToRelativeTime(offset,
       this.timeHeaderEl.offsetWidth);
-    this.emit("current-time-changed", time);
+
+    this.emit("timeline-data-changed", {
+      isPaused: true,
+      isMoving: false,
+      time: time
+    });
   },
 
   render: function(animations, documentCurrentTime) {
     this.unrender();
 
     this.animations = animations;
     if (!this.animations.length) {
       return;
@@ -857,34 +603,56 @@ AnimationsTimeline.prototype = {
     if (!documentCurrentTime) {
       this.scrubberEl.style.display = "none";
     } else {
       this.scrubberEl.style.display = "block";
       this.startAnimatingScrubber(documentCurrentTime);
     }
   },
 
+  isAtLeastOneAnimationPlaying: function() {
+    return this.animations.some(({state}) => state.playState === "running");
+  },
+
   startAnimatingScrubber: function(time) {
     let x = TimeScale.startTimeToDistance(time, this.timeHeaderEl.offsetWidth);
     this.scrubberEl.style.left = x + "px";
 
     if (time < TimeScale.minStartTime ||
-        time > TimeScale.maxEndTime) {
+        time > TimeScale.maxEndTime ||
+        !this.isAtLeastOneAnimationPlaying()) {
+      this.stopAnimatingScrubber();
+      this.emit("timeline-data-changed", {
+        isPaused: false,
+        isMoving: false,
+        time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth)
+      });
       return;
     }
 
+    this.emit("timeline-data-changed", {
+      isPaused: false,
+      isMoving: true,
+      time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth)
+    });
+
     let now = this.win.performance.now();
     this.rafID = this.win.requestAnimationFrame(() => {
+      if (!this.rafID) {
+        // In case the scrubber was stopped in the meantime.
+        return;
+      }
       this.startAnimatingScrubber(time + this.win.performance.now() - now);
     });
   },
 
   stopAnimatingScrubber: function() {
     if (this.rafID) {
       this.win.cancelAnimationFrame(this.rafID);
+      this.rafID = null;
     }
   },
 
   onAnimationStateChanged: function() {
     // For now, simply re-render the component. The animation front's state has
     // already been updated.
     this.render(this.animations);
   },
@@ -930,17 +698,17 @@ AnimationsTimeline.prototype = {
   },
 
   drawTimeBlock: function({state}, el) {
     let width = el.offsetWidth;
 
     // Create a container element to hold the delay and iterations.
     // It is positioned according to its delay (divided by the playbackrate),
     // and its width is according to its duration (divided by the playbackrate).
-    let start = state.startTime;
+    let start = state.previousStartTime || 0;
     let duration = state.duration;
     let rate = state.playbackRate;
     let count = state.iterationCount;
     let delay = state.delay || 0;
 
     let x = TimeScale.startTimeToDistance(start + (delay / rate), width);
     let w = TimeScale.durationToDistance(duration / rate, width);
 
--- a/browser/devtools/animationinspector/test/browser.ini
+++ b/browser/devtools/animationinspector/test/browser.ini
@@ -6,53 +6,34 @@ support-files =
   doc_frame_script.js
   doc_modify_playbackRate.html
   doc_negative_animation.html
   doc_simple_animation.html
   head.js
 
 [browser_animation_controller_exposes_document_currentTime.js]
 [browser_animation_empty_on_invalid_nodes.js]
-[browser_animation_iterationCount_hidden_by_default.js]
 [browser_animation_mutations_with_same_names.js]
 [browser_animation_panel_exists.js]
 [browser_animation_participate_in_inspector_update.js]
-[browser_animation_play_pause_button.js]
 [browser_animation_playerFronts_are_refreshed.js]
 [browser_animation_playerWidgets_appear_on_panel_init.js]
-[browser_animation_playerWidgets_compositor_icon.js]
-[browser_animation_playerWidgets_destroy.js]
-[browser_animation_playerWidgets_disables_on_finished.js]
-[browser_animation_playerWidgets_dont_show_time_after_duration.js]
-[browser_animation_playerWidgets_have_control_buttons.js]
-[browser_animation_playerWidgets_meta_data.js]
-[browser_animation_playerWidgets_scrubber_delayed.js]
-[browser_animation_playerWidgets_scrubber_enabled.js]
-[browser_animation_playerWidgets_scrubber_moves.js]
-[browser_animation_playerWidgets_state_after_pause.js]
 [browser_animation_playerWidgets_target_nodes.js]
-[browser_animation_rate_select_shows_presets.js]
 [browser_animation_refresh_on_added_animation.js]
 [browser_animation_refresh_on_removed_animation.js]
 [browser_animation_refresh_when_active.js]
 [browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
-[browser_animation_setting_currentTime_works_and_pauses.js]
-[browser_animation_setting_playbackRate_works.js]
 [browser_animation_shows_player_on_valid_node.js]
 [browser_animation_target_highlight_select.js]
-[browser_animation_timeline_displays_with_pref.js]
 [browser_animation_timeline_header.js]
+[browser_animation_timeline_pause_button.js]
 [browser_animation_timeline_scrubber_exists.js]
 [browser_animation_timeline_scrubber_movable.js]
 [browser_animation_timeline_scrubber_moves.js]
 [browser_animation_timeline_shows_delay.js]
 [browser_animation_timeline_shows_iterations.js]
 [browser_animation_timeline_shows_time_info.js]
 [browser_animation_timeline_takes_rate_into_account.js]
 [browser_animation_timeline_ui.js]
 [browser_animation_toggle_button_resets_on_navigate.js]
 [browser_animation_toggle_button_toggles_animations.js]
-[browser_animation_toggle_button_updates_playerWidgets.js]
 [browser_animation_toolbar_exists.js]
-[browser_animation_ui_updates_when_animation_changes.js]
 [browser_animation_ui_updates_when_animation_data_changes.js]
-[browser_animation_ui_updates_when_animation_rate_changes.js]
-[browser_animation_ui_updates_when_animation_time_changes.js]
--- a/browser/devtools/animationinspector/test/browser_animation_controller_exposes_document_currentTime.js
+++ b/browser/devtools/animationinspector/test/browser_animation_controller_exposes_document_currentTime.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // Test that the controller provides the document.timeline currentTime (at least
 // the last known version since new animations were added).
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel, controller} = yield openAnimationInspectorNewUI();
+  let {panel, controller} = yield openAnimationInspector();
 
   ok(controller.documentCurrentTime, "The documentCurrentTime getter exists");
   checkDocumentTimeIsCorrect(controller);
   let time1 = controller.documentCurrentTime;
 
   yield startNewAnimation(controller, panel);
   checkDocumentTimeIsCorrect(controller);
   let time2 = controller.documentCurrentTime;
--- a/browser/devtools/animationinspector/test/browser_animation_empty_on_invalid_nodes.js
+++ b/browser/devtools/animationinspector/test/browser_animation_empty_on_invalid_nodes.js
@@ -6,46 +6,32 @@
 
 // Test that the panel shows no animation data for invalid or not animated nodes
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let {inspector, panel} = yield openAnimationInspector();
   yield testEmptyPanel(inspector, panel);
-
-  ({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
-  yield testEmptyPanel(inspector, panel, true);
 });
 
-function* testEmptyPanel(inspector, panel, isNewUI=false) {
+function* testEmptyPanel(inspector, panel) {
   info("Select node .still and check that the panel is empty");
   let stillNode = yield getNodeFront(".still", inspector);
   let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
   yield selectNode(stillNode, inspector);
   yield onUpdated;
 
-  if (isNewUI) {
-    is(panel.animationsTimelineComponent.animations.length, 0,
-       "No animation players stored in the timeline component for a still node");
-    is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
-       "No animation displayed in the timeline component for a still node");
-  } else {
-    ok(!panel.playerWidgets || !panel.playerWidgets.length,
-       "No player widgets displayed for a still node");
-  }
+  is(panel.animationsTimelineComponent.animations.length, 0,
+     "No animation players stored in the timeline component for a still node");
+  is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
+     "No animation displayed in the timeline component for a still node");
 
   info("Select the comment text node and check that the panel is empty");
   let commentNode = yield inspector.walker.previousSibling(stillNode);
   onUpdated = panel.once(panel.UI_UPDATED_EVENT);
   yield selectNode(commentNode, inspector);
   yield onUpdated;
 
-  if (isNewUI) {
-    is(panel.animationsTimelineComponent.animations.length, 0,
-       "No animation players stored in the timeline component for a text node");
-    is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
-       "No animation displayed in the timeline component for a text node");
-  } else {
-    ok(!panel.playerWidgets || !panel.playerWidgets.length,
-       "No player widgets displayed for a text node");
-  }
-}
+  is(panel.animationsTimelineComponent.animations.length, 0,
+     "No animation players stored in the timeline component for a text node");
+  is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 0,
+     "No animation displayed in the timeline component for a text node");}
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_iterationCount_hidden_by_default.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Check that iteration count is only shown in the UI when it's different than 1
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Selecting a node with an animation that doesn't repeat");
-  yield selectNode(".long", inspector);
-  let widget = panel.playerWidgets[0];
-
-  ok(isNodeVisible(widget.metaDataComponent.durationValue),
-    "The duration value is shown");
-  ok(!isNodeVisible(widget.metaDataComponent.delayValue),
-    "The delay value is hidden");
-  ok(!isNodeVisible(widget.metaDataComponent.iterationValue),
-    "The iteration count is hidden");
-
-  info("Selecting a node with an animation that repeats several times");
-  yield selectNode(".delayed", inspector);
-  widget = panel.playerWidgets[0];
-
-  ok(isNodeVisible(widget.metaDataComponent.durationValue),
-    "The duration value is shown");
-  ok(isNodeVisible(widget.metaDataComponent.delayValue),
-    "The delay value is shown");
-  ok(isNodeVisible(widget.metaDataComponent.iterationValue),
-    "The iteration count is shown");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_mutations_with_same_names.js
+++ b/browser/devtools/animationinspector/test/browser_animation_mutations_with_same_names.js
@@ -6,17 +6,17 @@
 
 // Check that when animations are added later (through animation mutations) and
 // if these animations have the same names, then all of them are still being
 // displayed (which should be true as long as these animations apply to
 // different nodes).
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_negative_animation.html");
-  let {controller, panel} = yield openAnimationInspectorNewUI();
+  let {controller, panel} = yield openAnimationInspector();
 
   info("Wait until all animations have been added " +
        "(they're added with setTimeout)");
   while (controller.animationPlayers.length < 3) {
     yield controller.once(controller.PLAYERS_UPDATED_EVENT);
   }
   yield waitForAllAnimationTargets(panel);
 
--- a/browser/devtools/animationinspector/test/browser_animation_panel_exists.js
+++ b/browser/devtools/animationinspector/test/browser_animation_panel_exists.js
@@ -10,18 +10,10 @@ add_task(function*() {
   yield addTab("data:text/html;charset=utf-8,welcome to the animation panel");
   let {panel, controller} = yield openAnimationInspector();
 
   ok(controller, "The animation controller exists");
   ok(controller.animationsFront, "The animation controller has been initialized");
 
   ok(panel, "The animation panel exists");
   ok(panel.playersEl, "The animation panel has been initialized");
-
-  ({panel, controller} = yield closeAnimationInspectorAndRestartWithNewUI());
-
-  ok(controller, "The animation controller exists");
-  ok(controller.animationsFront, "The animation controller has been initialized");
-
-  ok(panel, "The animation panel exists");
-  ok(panel.playersEl, "The animation panel has been initialized");
   ok(panel.animationsTimelineComponent, "The animation panel has been initialized");
 });
--- a/browser/devtools/animationinspector/test/browser_animation_participate_in_inspector_update.js
+++ b/browser/devtools/animationinspector/test/browser_animation_participate_in_inspector_update.js
@@ -8,19 +8,16 @@
 // inspector-updated event. This means that the test verifies that the
 // inspector-updated event is emitted *after* the animation panel is ready.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let ui = yield openAnimationInspector();
   yield testEventsOrder(ui);
-
-  ui = yield closeAnimationInspectorAndRestartWithNewUI();
-  yield testEventsOrder(ui);
 });
 
 function* testEventsOrder({inspector, panel, controller}) {
   info("Listen for the players-updated, ui-updated and inspector-updated events");
   let receivedEvents = [];
   controller.once(controller.PLAYERS_UPDATED_EVENT, () => {
     receivedEvents.push(controller.PLAYERS_UPDATED_EVENT);
   });
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_play_pause_button.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Check that the play/pause button actually plays and pauses the player.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel, controller} = yield openAnimationInspector();
-
-  info("Selecting an animated node");
-  yield selectNode(".animated", inspector);
-
-  let player = controller.animationPlayers[0];
-  let widget = panel.playerWidgets[0];
-
-  info("Click the pause button");
-  yield togglePlayPauseButton(widget);
-
-  is(player.state.playState, "paused", "The AnimationPlayerFront is paused");
-  ok(widget.el.classList.contains("paused"), "The button's state has changed");
-  ok(!widget.rafID, "The smooth timeline animation has been stopped");
-
-  info("Click on the play button");
-  yield togglePlayPauseButton(widget);
-
-  is(player.state.playState, "running", "The AnimationPlayerFront is running");
-  ok(widget.el.classList.contains("running"), "The button's state has changed");
-  ok(widget.rafID, "The smooth timeline animation has been started");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_playerFronts_are_refreshed.js
+++ b/browser/devtools/animationinspector/test/browser_animation_playerFronts_are_refreshed.js
@@ -14,27 +14,22 @@ add_task(function*() {
   info("Selecting an animated node");
   // selectNode waits for the inspector-updated event before resolving, which
   // means the controller.PLAYERS_UPDATED_EVENT event has been emitted before
   // and players are ready.
   yield selectNode(".animated", inspector);
 
   is(controller.animationPlayers.length, 1,
     "One AnimationPlayerFront has been created");
-  ok(controller.animationPlayers[0].autoRefreshTimer,
-    "The AnimationPlayerFront has been set to auto-refresh");
 
   info("Selecting a node with mutliple animations");
   yield selectNode(".multi", inspector);
 
   is(controller.animationPlayers.length, 2,
     "2 AnimationPlayerFronts have been created");
-  ok(controller.animationPlayers[0].autoRefreshTimer &&
-     controller.animationPlayers[1].autoRefreshTimer,
-    "The AnimationPlayerFronts have been set to auto-refresh");
 
   // Hold on to one of the AnimationPlayerFront objects and mock its release
   // method to test that it is released correctly and that its auto-refresh is
   // stopped.
   let retainedFront = controller.animationPlayers[0];
   let oldRelease = retainedFront.release;
   let releaseCalled = false;
   retainedFront.release = () => {
@@ -44,12 +39,11 @@ add_task(function*() {
   info("Selecting a node with no animations");
   yield selectNode(".still", inspector);
 
   is(controller.animationPlayers.length, 0,
     "There are no more AnimationPlayerFront objects");
 
   info("Checking the destroyed AnimationPlayerFront object");
   ok(releaseCalled, "The AnimationPlayerFront has been released");
-  ok(!retainedFront.autoRefreshTimer,
-    "The released AnimationPlayerFront's auto-refresh mode has been turned off");
+
   yield oldRelease.call(retainedFront);
 });
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_appear_on_panel_init.js
+++ b/browser/devtools/animationinspector/test/browser_animation_playerWidgets_appear_on_panel_init.js
@@ -6,17 +6,13 @@
 
 // Test that player widgets are displayed right when the animation panel is
 // initialized, if the selected node (<body> by default) is animated.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_body_animation.html");
 
   let {panel} = yield openAnimationInspector();
-  is(panel.playerWidgets.length, 1,
-    "One animation player is displayed after init");
-
-  ({panel} = yield closeAnimationInspectorAndRestartWithNewUI());
   is(panel.animationsTimelineComponent.animations.length, 1,
     "One animation is handled by the timeline after init");
   is(panel.animationsTimelineComponent.animationsEl.childNodes.length, 1,
     "One animation is displayed after init");
 });
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_compositor_icon.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that player widgets show the right player meta-data for the
-// isRunningOnCompositor property.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select the simple animated node");
-  yield selectNode(".animated", inspector);
-
-  let compositorEl = panel.playerWidgets[0]
-                     .el.querySelector(".compositor-icon");
-
-  ok(compositorEl, "The compositor-icon element exists");
-  ok(isNodeVisible(compositorEl),
-     "The compositor icon is visible, since the animation is running on " +
-     "compositor thread");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_destroy.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that player widgets are destroyed correctly when needed.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select an animated node");
-  yield selectNode(".multi", inspector);
-
-  info("Hold on to one of the player widget instances to test it after destroy");
-  let widget = panel.playerWidgets[0];
-
-  info("Select another node to get the previous widgets destroyed");
-  yield selectNode(".animated", inspector);
-
-  ok(widget.destroyed, "The widget's destroyed flag is true");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_disables_on_finished.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that when animations end, the corresponding player widgets are disabled.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel, controller} = yield openAnimationInspector();
-
-  info("Select the test node");
-  yield selectNode(".still", inspector);
-
-  info("Apply 2 finite animations to the test node and wait for the widgets to appear");
-  let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
-  yield executeInContent("devtools:test:setAttribute", {
-    selector: ".still",
-    attributeName: "class",
-    attributeValue: "ball still multi-finite"
-  });
-  yield onUiUpdated;
-
-  is(controller.animationPlayers.length, 2, "2 animation players exist");
-
-  info("Wait for both animations to end");
-
-  let promises = controller.animationPlayers.map(front => {
-    return waitForPlayState(front, "finished");
-  });
-
-  yield promise.all(promises);
-
-  for (let widgetEl of panel.playersEl.querySelectorAll(".player-widget")) {
-    ok(widgetEl.classList.contains("finished"), "The player widget has the right class");
-  }
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_dont_show_time_after_duration.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that after the animation has ended, the current time label and timeline
-// slider don't show values bigger than the animation duration (which would
-// happen if the local requestAnimationFrame loop didn't stop correctly).
-
-var L10N = new ViewHelpers.L10N();
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select the test node");
-  yield selectNode(".still", inspector);
-
-  info("Start an animation on the test node and wait for the widget to appear");
-  let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
-  yield executeInContent("devtools:test:setAttribute", {
-    selector: ".still",
-    attributeName: "class",
-    attributeValue: "ball still short"
-  });
-  yield onUiUpdated;
-
-  info("Wait until the animation ends");
-  let widget = panel.playerWidgets[0];
-  let front = widget.player;
-
-  yield waitForPlayState(front, "finished");
-
-  is(widget.currentTimeEl.value, front.state.duration,
-    "The timeline slider has the right value");
-  is(widget.timeDisplayEl.textContent,
-    L10N.numberWithDecimals(front.state.duration / 1000, 2) + "s",
-    "The timeline slider has the right value");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_have_control_buttons.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that playerWidgets have control buttons: play/pause, rewind, fast-forward.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {controller, inspector, panel} = yield openAnimationInspector();
-
-  info("Select the simple animated node");
-  yield selectNode(".animated", inspector);
-
-  let widget = panel.playerWidgets[0];
-  let container = widget.el.querySelector(".playback-controls");
-
-  ok(container, "The control buttons container exists");
-  is(container.querySelectorAll("button").length, 3,
-    "The container contains 3 buttons");
-  ok(container.children[0].classList.contains("toggle"),
-    "The first button is the play/pause button");
-  ok(container.children[1].classList.contains("rw"),
-    "The second button is the rewind button");
-  ok(container.children[2].classList.contains("ff"),
-    "The third button is the fast-forward button");
-  ok(container.querySelector("select"),
-    "The container contains the playback rate select");
-
-  info("Faking an older server version by setting " +
-    "AnimationsController.traits.hasSetCurrentTime to false");
-
-  // Selecting <div.still> to make sure no widgets are displayed in the panel.
-  yield selectNode(".still", inspector);
-  controller.traits.hasSetCurrentTime = false;
-
-  info("Selecting the animated node again");
-  yield selectNode(".animated", inspector);
-
-  widget = panel.playerWidgets[0];
-  container = widget.el.querySelector(".playback-controls");
-
-  ok(container, "The control buttons container still exists");
-  is(container.querySelectorAll("button").length, 1,
-    "The container only contains 1 button");
-  ok(container.children[0].classList.contains("toggle"),
-    "The first button is the play/pause button");
-
-  yield selectNode(".still", inspector);
-  controller.traits.hasSetCurrentTime = true;
-
-  info("Faking an older server version by setting " +
-    "AnimationsController.traits.hasSetPlaybackRate to false");
-
-  controller.traits.hasSetPlaybackRate = false;
-
-  info("Selecting the animated node again");
-  yield selectNode(".animated", inspector);
-
-  widget = panel.playerWidgets[0];
-  container = widget.el.querySelector(".playback-controls");
-
-  ok(container, "The control buttons container still exists");
-  ok(!container.querySelector("select"),
-    "The playback rate select does not exist");
-
-  yield selectNode(".still", inspector);
-  controller.traits.hasSetPlaybackRate = true;
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_meta_data.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that player widgets show the right player meta-data (name, duration,
-// iteration count, delay).
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select the simple animated node");
-  yield selectNode(".animated", inspector);
-
-  let titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
-  ok(titleEl,
-    "The player widget has a title element, where meta-data should be displayed");
-
-  let nameEl = titleEl.querySelector("strong");
-  ok(nameEl, "The first <strong> tag was retrieved, it should contain the name");
-  is(nameEl.textContent, "simple-animation", "The animation name is correct");
-
-  let metaDataEl = titleEl.querySelector(".meta-data");
-  ok(metaDataEl, "The meta-data element exists");
-
-  let metaDataEls = metaDataEl.querySelectorAll("strong");
-  is(metaDataEls.length, 3, "3 meta-data elements were found");
-  is(metaDataEls[0].textContent, "2s",
-    "The first meta-data is the duration, and is correct");
-  ok(!isNodeVisible(metaDataEls[1]),
-    "The second meta-data is hidden, since there's no delay on the animation");
-
-  info("Select the node with the delayed animation");
-  yield selectNode(".delayed", inspector);
-
-  titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
-  nameEl = titleEl.querySelector("strong");
-  is(nameEl.textContent, "simple-animation", "The animation name is correct");
-
-  metaDataEls = titleEl.querySelectorAll(".meta-data strong");
-  is(metaDataEls.length, 3,
-    "3 meta-data elements were found for the delayed animation");
-  is(metaDataEls[0].textContent, "3s",
-    "The first meta-data is the duration, and is correct");
-  ok(isNodeVisible(metaDataEls[0]), "The duration is shown");
-  is(metaDataEls[1].textContent, "60s",
-    "The second meta-data is the delay, and is correct");
-  ok(isNodeVisible(metaDataEls[1]), "The delay is shown");
-  is(metaDataEls[2].textContent, "10",
-    "The third meta-data is the iteration count, and is correct");
-  ok(isNodeVisible(metaDataEls[2]), "The iteration count is shown");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_scrubber_delayed.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that the currentTime timeline doesn't move if the animation is currently
-// waiting for an animation-delay.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select the delayed animation node");
-  yield selectNode(".delayed", inspector);
-
-  let widget = panel.playerWidgets[0];
-
-  let timeline = widget.currentTimeEl;
-  is(timeline.value, 0, "The timeline is at 0 since the animation hasn't started");
-
-  let timeLabel = widget.timeDisplayEl;
-  is(timeLabel.textContent, "0s", "The current time is 0");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_scrubber_enabled.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that the currentTime timeline widget is enabled and that the associated
-// player front supports setting the current time.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {controller, inspector, panel} = yield openAnimationInspector();
-
-  info("Select the animated node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget's timeline element");
-  let widget = panel.playerWidgets[0];
-  let timeline = widget.currentTimeEl;
-
-  ok(!timeline.hasAttribute("disabled"), "The timeline input[range] is enabled");
-  ok(widget.setCurrentTime, "The widget has the setCurrentTime method");
-  ok(widget.player.setCurrentTime,
-     "The associated player front has the setCurrentTime method");
-
-  info("Faking an older server version by setting " +
-       "AnimationsController.traits.hasSetCurrentTime to false");
-
-  yield selectNode("body", inspector);
-  controller.traits.hasSetCurrentTime = false;
-
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget's timeline element");
-  widget = panel.playerWidgets[0];
-  timeline = widget.currentTimeEl;
-
-  ok(timeline.hasAttribute("disabled"), "The timeline input[range] is disabled");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_scrubber_moves.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that the currentTime timeline widget actually progresses with the
-// animation itself.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select the animated node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget's timeline element and its current position");
-  let widget = panel.playerWidgets[0];
-  let timeline = widget.currentTimeEl;
-
-  yield onceNextPlayerRefresh(widget.player);
-  ok(widget.rafID, "The widget is updating the timeline with a rAF loop");
-
-  info("Pause the animation");
-  yield togglePlayPauseButton(widget);
-
-  ok(!widget.rafID, "The rAF loop has been stopped after the animation was paused");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_state_after_pause.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that once an animation is paused and its widget is refreshed, the right
-// initial time is displayed.
-
-var L10N = new ViewHelpers.L10N();
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Selecting the test node");
-  yield selectNode(".animated", inspector);
-
-  info("Pausing the animation by using the widget");
-  let widget = panel.playerWidgets[0];
-  yield widget.pause();
-
-  info("Selecting another node and then the same node again to refresh the widget");
-  yield selectNode(".still", inspector);
-  yield selectNode(".animated", inspector);
-
-  widget = panel.playerWidgets[0];
-  ok(widget.el.classList.contains("paused"), "The widget is still in paused mode");
-  is(widget.timeDisplayEl.textContent,
-    L10N.numberWithDecimals(widget.player.state.currentTime / 1000, 2) + "s",
-    "The initial time has been set to the player's");
-});
-
--- a/browser/devtools/animationinspector/test/browser_animation_playerWidgets_target_nodes.js
+++ b/browser/devtools/animationinspector/test/browser_animation_playerWidgets_target_nodes.js
@@ -8,45 +8,22 @@
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   info("Select the simple animated node");
   yield selectNode(".animated", inspector);
 
-  let widget = panel.playerWidgets[0];
-
-  // Make sure to wait for the target-retrieved event if the nodeFront hasn't
-  // yet been retrieved by the TargetNodeComponent.
-  if (!widget.targetNodeComponent.nodeFront) {
-    yield widget.targetNodeComponent.once("target-retrieved");
-  }
-
-  let targetEl = widget.el.querySelector(".animation-target");
-  ok(targetEl, "The player widget has a target element");
-  is(targetEl.textContent, "<divid=\"\"class=\"ball animated\">",
-    "The target element's content is correct");
-
-  let selectorEl = targetEl.querySelector(".node-selector");
-  ok(selectorEl,
-    "The icon to select the target element in the inspector exists");
-
-  info("Test again with the new timeline UI");
-  ({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
-
-  info("Select the simple animated node");
-  yield selectNode(".animated", inspector);
-
   let targetNodeComponent = panel.animationsTimelineComponent.targetNodes[0];
   // Make sure to wait for the target-retrieved event if the nodeFront hasn't
   // yet been retrieved by the TargetNodeComponent.
   if (!targetNodeComponent.nodeFront) {
     yield targetNodeComponent.once("target-retrieved");
   }
 
   is(targetNodeComponent.el.textContent, "div#.ball.animated",
     "The target element's content is correct");
 
-  selectorEl = targetNodeComponent.el.querySelector(".node-selector");
+  let selectorEl = targetNodeComponent.el.querySelector(".node-selector");
   ok(selectorEl,
     "The icon to select the target element in the inspector exists");
 });
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_rate_select_shows_presets.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that the playbackRate select element contains a list of presets and
-// and that if the animation has a current rate that is not part of this list,
-// it is added.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Selecting the test node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the playback rate UI component");
-  let widget = panel.playerWidgets[0];
-  let rateComponent = widget.rateComponent;
-
-  let options = rateComponent.el.querySelectorAll("option");
-  is(options.length, rateComponent.PRESETS.length,
-    "The playback rate select contains the right number of options");
-
-  for (let i = 0; i < rateComponent.PRESETS.length; i ++) {
-    is(options[i].value, rateComponent.PRESETS[i] + "",
-      "The playback rate option " + i + " has the right preset value " +
-      rateComponent.PRESETS[i]);
-  }
-
-  info("Set a custom rate (not part of the presets) via the DOM");
-  let onRateChanged = waitForStateCondition(widget.player, state => {
-    return state.playbackRate === 3.6
-  });
-  yield executeInContent("Test:SetAnimationPlayerPlaybackRate", {
-    selector: ".animated",
-    animationIndex: 0,
-    playbackRate: 3.6
-  });
-  yield onRateChanged;
-
-  options = rateComponent.el.querySelectorAll("option");
-  is(options.length, rateComponent.PRESETS.length + 1,
-    "The playback rate select contains the right number of options (presets + 1)");
-
-  ok([...options].find(option => option.value === "3.6"),
-    "The custom rate is part of the select");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_refresh_on_added_animation.js
+++ b/browser/devtools/animationinspector/test/browser_animation_refresh_on_added_animation.js
@@ -6,19 +6,16 @@
 
 // Test that the panel content refreshes when new animations are added.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let {inspector, panel} = yield openAnimationInspector();
   yield testRefreshOnNewAnimation(inspector, panel);
-
-  ({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
-  yield testRefreshOnNewAnimation(inspector, panel);
 });
 
 function* testRefreshOnNewAnimation(inspector, panel) {
   info("Select a non animated node");
   yield selectNode(".still", inspector);
 
   assertAnimationsDisplayed(panel, 0);
 
--- a/browser/devtools/animationinspector/test/browser_animation_refresh_on_removed_animation.js
+++ b/browser/devtools/animationinspector/test/browser_animation_refresh_on_removed_animation.js
@@ -6,21 +6,16 @@
 
 // Test that the panel content refreshes when animations are removed.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let {inspector, panel} = yield openAnimationInspector();
   yield testRefreshOnRemove(inspector, panel);
-  yield testAddedAnimationWorks(inspector, panel);
-
-  info("Reload and test again with the new UI");
-  ({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI(true));
-  yield testRefreshOnRemove(inspector, panel, true);
 });
 
 function* testRefreshOnRemove(inspector, panel) {
   info("Select a animated node");
   yield selectNode(".animated", inspector);
 
   assertAnimationsDisplayed(panel, 1);
 
@@ -46,27 +41,8 @@ function* testRefreshOnRemove(inspector,
     attributeName: "class",
     attributeValue: "ball short test-node"
   });
   yield onPanelUpdated;
   yield waitForAllAnimationTargets(panel);
 
   assertAnimationsDisplayed(panel, 1);
 }
-
-function* testAddedAnimationWorks(inspector, panel) {
-  info("Now wait until the animation finishes");
-  let widget = panel.playerWidgets[0];
-  yield waitForPlayState(widget.player, "finished");
-
-  is(panel.playersEl.querySelectorAll(".player-widget").length, 1,
-    "There is still a player widget in the panel after the animation finished");
-
-  info("Checking that the animation's currentTime can still be set");
-  info("Click at the center of the slider input");
-
-  let onPaused = waitForPlayState(widget.player, "paused");
-  let input = widget.currentTimeEl;
-  let win = input.ownerDocument.defaultView;
-  EventUtils.synthesizeMouseAtCenter(input, {type: "mousedown"}, win);
-  yield onPaused;
-  ok(widget.el.classList.contains("paused"), "The widget is in paused mode");
-}
--- a/browser/devtools/animationinspector/test/browser_animation_refresh_when_active.js
+++ b/browser/devtools/animationinspector/test/browser_animation_refresh_when_active.js
@@ -6,19 +6,16 @@
 
 // Test that the panel only refreshes when it is visible in the sidebar.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let {inspector, panel} = yield openAnimationInspector();
   yield testRefresh(inspector, panel);
-
-  ({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
-  yield testRefresh(inspector, panel);
 });
 
 function* testRefresh(inspector, panel) {
   info("Select a non animated node");
   yield selectNode(".still", inspector);
 
   info("Switch to the rule-view panel");
   inspector.sidebar.select("ruleview");
--- a/browser/devtools/animationinspector/test/browser_animation_same_nb_of_playerWidgets_and_playerFronts.js
+++ b/browser/devtools/animationinspector/test/browser_animation_same_nb_of_playerWidgets_and_playerFronts.js
@@ -5,32 +5,16 @@
 "use strict";
 
 // Check that when playerFronts are updated, the same number of playerWidgets
 // are created in the panel.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel, controller} = yield openAnimationInspector();
-
-  info("Selecting the test animated node");
-  yield selectNode(".multi", inspector);
-
-  is(controller.animationPlayers.length, panel.playerWidgets.length,
-    "As many playerWidgets were created as there are playerFronts");
-
-  for (let widget of panel.playerWidgets) {
-    ok(widget.initialized, "The player widget is initialized");
-    is(widget.el.parentNode, panel.playersEl,
-      "The player widget has been appended to the panel");
-  }
-
-  info("Test again with the new UI, making sure the same number of " +
-       "animation timelines is created");
-  ({inspector, panel, controller} = yield closeAnimationInspectorAndRestartWithNewUI());
   let timeline = panel.animationsTimelineComponent;
 
   info("Selecting the test animated node again");
   yield selectNode(".multi", inspector);
 
   is(controller.animationPlayers.length,
     timeline.animationsEl.querySelectorAll(".animation").length,
     "As many timeline elements were created as there are playerFronts");
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_setting_currentTime_works_and_pauses.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that setting an animation's current time by clicking in the input[range]
-// or rewind or fast-forward buttons pauses the animation and sets it to the
-// right time.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {toolbox, inspector, panel} = yield openAnimationInspector();
-
-  info("Select an animated node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget for this node");
-  let widget = panel.playerWidgets[0];
-  let input = widget.currentTimeEl;
-  let rwBtn = widget.rewindBtnEl;
-  let ffBtn = widget.fastForwardBtnEl;
-  let win = input.ownerDocument.defaultView;
-
-  info("Click at the center of the input")
-  EventUtils.synthesizeMouseAtCenter(input, {type: "mousedown"}, win);
-
-  yield checkPausedAt(widget, 1000);
-
-  info("Resume the player and wait for an auto-refresh event");
-  yield widget.player.play();
-  yield onceNextPlayerRefresh(widget.player);
-
-  info("Click on the rewind button");
-  EventUtils.sendMouseEvent({type: "click"}, rwBtn, win);
-
-  yield checkPausedAt(widget, 0);
-
-  info("Click on the fast-forward button");
-  EventUtils.sendMouseEvent({type: "click"}, ffBtn, win);
-
-  yield checkPausedAt(widget, 2000);
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_setting_playbackRate_works.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that setting an animation's playback rate by selecting a rate in the
-// presets drop-down sets the rate accordingly.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {toolbox, inspector, panel} = yield openAnimationInspector();
-
-  info("Select an animated node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget for this node");
-  let widget = panel.playerWidgets[0];
-  let select = widget.rateComponent.el;
-  let win = select.ownerDocument.defaultView;
-
-  info("Click on the rate drop-down");
-  EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"}, win);
-
-  info("Click on a rate option");
-  let option = select.options[select.options.length - 1];
-  EventUtils.synthesizeMouseAtCenter(option, {type: "mouseup"}, win);
-  let selectedRate = parseFloat(option.value);
-
-  info("Check that the rate was changed on the player at the next update");
-  yield waitForStateCondition(widget.player, ({playbackRate}) => playbackRate === selectedRate);
-  is(widget.player.state.playbackRate, selectedRate,
-    "The rate was changed successfully");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_shows_player_on_valid_node.js
+++ b/browser/devtools/animationinspector/test/browser_animation_shows_player_on_valid_node.js
@@ -4,23 +4,16 @@
 
 "use strict";
 
 // Test that the panel shows an animation player when an animated node is
 // selected.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-
   let {inspector, panel} = yield openAnimationInspector();
-  yield testShowsAnimations(inspector, panel);
 
-  ({inspector, panel} = yield closeAnimationInspectorAndRestartWithNewUI());
-  yield testShowsAnimations(inspector, panel);
-});
-
-function* testShowsAnimations(inspector, panel) {
   info("Select node .animated and check that the panel is not empty");
   let node = yield getNodeFront(".animated", inspector);
   yield selectNode(node, inspector);
 
   assertAnimationsDisplayed(panel, 1);
-}
+});
--- a/browser/devtools/animationinspector/test/browser_animation_target_highlight_select.js
+++ b/browser/devtools/animationinspector/test/browser_animation_target_highlight_select.js
@@ -7,22 +7,19 @@
 // Test that the DOM element targets displayed in animation player widgets can
 // be used to highlight elements in the DOM and select them in the inspector.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let ui = yield openAnimationInspector();
   yield testTargetNode(ui);
-
-  ui = yield closeAnimationInspectorAndRestartWithNewUI();
-  yield testTargetNode(ui, true);
 });
 
-function* testTargetNode({toolbox, inspector, panel}, isNewUI) {
+function* testTargetNode({toolbox, inspector, panel}) {
   info("Select the simple animated node");
 
   let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
   yield selectNode(".animated", inspector);
   yield onPanelUpdated;
 
   let targets = yield waitForAllAnimationTargets(panel);
   // Arbitrary select the first one
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_displays_with_pref.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Check that the timeline-based UI is displayed instead of the playerwidget-
-// based UI when the "devtools.inspector.animationInspectorV3" is set.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspectorNewUI();
-
-  info("Selecting the test node");
-  yield selectNode(".animated", inspector);
-
-  let timeline = panel.animationsTimelineComponent;
-
-  ok(timeline, "The timeline components was created");
-  is(timeline.rootWrapperEl.parentNode, panel.playersEl,
-    "The timeline component was appended in the DOM");
-  is(panel.playersEl.querySelectorAll(".player-widget").length, 0,
-    "There are no playerWidgets in the DOM");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_header.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_header.js
@@ -1,25 +1,24 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the timeline-based UI shows correct time graduations in the
-// header.
+// Check that the timeline shows correct time graduations in the header.
 
 const {findOptimalTimeInterval} = require("devtools/animationinspector/utils");
 const {TimeScale} = require("devtools/animationinspector/components");
 // Should be kept in sync with TIME_GRADUATION_MIN_SPACING in components.js
 const TIME_GRADUATION_MIN_SPACING = 40;
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let headerEl = timeline.timeHeaderEl;
 
   info("Find out how many time graduations should there be");
   let width = headerEl.offsetWidth;
   let scale = width / (TimeScale.maxEndTime - TimeScale.minStartTime);
   // Note that findOptimalTimeInterval is tested separately in xpcshell test
new file mode 100644
--- /dev/null
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_pause_button.js
@@ -0,0 +1,67 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the timeline toolbar contains a pause button and that this pause
+// button can be clicked. Check that when it is, the current animations
+// displayed in the timeline get their playstates changed accordingly, and check
+// that the scrubber resumes/stops moving.
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+
+  let {panel} = yield openAnimationInspector();
+  let btn = panel.playTimelineButtonEl;
+
+  ok(btn, "The play/pause button exists");
+  ok(!btn.classList.contains("paused"),
+     "The play/pause button is in its playing state");
+
+  info("Click on the button to pause all timeline animations");
+  yield clickPlayPauseButton(panel);
+
+  ok(btn.classList.contains("paused"),
+     "The play/pause button is in its paused state");
+  yield checkIfScrubberMoving(panel, false);
+
+  info("Click again on the button to play all timeline animations");
+  yield clickPlayPauseButton(panel);
+
+  ok(!btn.classList.contains("paused"),
+     "The play/pause button is in its playing state again");
+  yield checkIfScrubberMoving(panel, true);
+});
+
+function* clickPlayPauseButton(panel) {
+  let onUiUpdated = panel.once(panel.UI_UPDATED_EVENT);
+
+  let btn = panel.playTimelineButtonEl;
+  let win = btn.ownerDocument.defaultView;
+  EventUtils.sendMouseEvent({type: "click"}, btn, win);
+
+  yield onUiUpdated;
+  yield waitForAllAnimationTargets(panel);
+}
+
+function* checkIfScrubberMoving(panel, isMoving) {
+  let timeline = panel.animationsTimelineComponent;
+  let scrubberEl = timeline.scrubberEl;
+
+  if (isMoving) {
+    // If we expect the scrubber to move, just wait for a couple of
+    // timeline-data-changed events and compare times.
+    let {time: time1} = yield timeline.once("timeline-data-changed");
+    let {time: time2} = yield timeline.once("timeline-data-changed");
+    ok(time2 > time1, "The scrubber is moving");
+  } else {
+    // If instead we expect the scrubber to remain at its position, just wait
+    // for some time. A relatively long timeout is used because the test page
+    // has long running animations, so the scrubber doesn't move that quickly.
+    let startOffset = scrubberEl.offsetLeft;
+    yield new Promise(r => setTimeout(r, 2000));
+    let endOffset = scrubberEl.offsetLeft;
+    is(startOffset, endOffset, "The scrubber is not moving");
+  }
+}
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_scrubber_exists.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_scrubber_exists.js
@@ -1,18 +1,18 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the timeline-based UI does have a scrubber element.
+// Check that the timeline does have a scrubber element.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let scrubberEl = timeline.scrubberEl;
 
   ok(scrubberEl, "The scrubber element exists");
   ok(scrubberEl.classList.contains("scrubber"), "It has the right classname");
 });
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_scrubber_movable.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_scrubber_movable.js
@@ -1,35 +1,53 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the scrubber in the timeline-based UI can be moved by clicking &
-// dragging in the header area.
+// Check that the scrubber in the timeline can be moved by clicking & dragging
+// in the header area.
+// Also check that doing so changes the timeline's play/pause button to paused
+// state.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let win = timeline.win;
   let timeHeaderEl = timeline.timeHeaderEl;
   let scrubberEl = timeline.scrubberEl;
+  let playTimelineButtonEl = panel.playTimelineButtonEl;
+
+  ok(!playTimelineButtonEl.classList.contains("paused"),
+     "The timeline play button is in its playing state by default");
 
   info("Mousedown in the header to move the scrubber");
-  EventUtils.synthesizeMouse(timeHeaderEl, 50, 1, {type: "mousedown"}, win);
+  yield synthesizeMouseAndWaitForTimelineChange(timeline, 50, 1, "mousedown");
   let newPos = parseInt(scrubberEl.style.left, 10);
   is(newPos, 50, "The scrubber moved on mousedown");
 
+  ok(playTimelineButtonEl.classList.contains("paused"),
+     "The timeline play button is in its paused state after mousedown");
+
   info("Continue moving the mouse and verify that the scrubber tracks it");
-  EventUtils.synthesizeMouse(timeHeaderEl, 100, 1, {type: "mousemove"}, win);
+  yield synthesizeMouseAndWaitForTimelineChange(timeline, 100, 1, "mousemove");
   newPos = parseInt(scrubberEl.style.left, 10);
   is(newPos, 100, "The scrubber followed the mouse");
 
+  ok(playTimelineButtonEl.classList.contains("paused"),
+     "The timeline play button is in its paused state after mousemove");
+
   info("Release the mouse and move again and verify that the scrubber stays");
   EventUtils.synthesizeMouse(timeHeaderEl, 100, 1, {type: "mouseup"}, win);
   EventUtils.synthesizeMouse(timeHeaderEl, 200, 1, {type: "mousemove"}, win);
   newPos = parseInt(scrubberEl.style.left, 10);
   is(newPos, 100, "The scrubber stopped following the mouse");
 });
+
+function* synthesizeMouseAndWaitForTimelineChange(timeline, x, y, type) {
+  let onDataChanged = timeline.once("timeline-data-changed");
+  EventUtils.synthesizeMouse(timeline.timeHeaderEl, x, y, {type}, timeline.win);
+  yield onDataChanged;
+}
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_scrubber_moves.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_scrubber_moves.js
@@ -1,23 +1,22 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the scrubber in the timeline-based UI moves when animations are
-// playing.
+// Check that the scrubber in the timeline moves when animations are playing.
 // The animations in the test page last for a very long time, so the test just
 // measures the position of the scrubber once, then waits for some time to pass
 // and measures its position again.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let scrubberEl = timeline.scrubberEl;
   let startPos = scrubberEl.getBoundingClientRect().left;
 
   info("Wait for some time to check that the scrubber moves");
   yield new Promise(r => setTimeout(r, 2000));
 
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_shows_delay.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_shows_delay.js
@@ -1,22 +1,22 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that animation delay is visualized in the timeline-based UI when the
-// animation is delayed.
+// Check that animation delay is visualized in the timeline when the animation
+// is delayed.
 // Also check that negative delays do not overflow the UI, and are shown like
 // positive delays.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspectorNewUI();
+  let {inspector, panel} = yield openAnimationInspector();
 
   info("Selecting a delayed animated node");
   yield selectNode(".delayed", inspector);
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
   checkDelayAndName(timelineEl, true);
 
   info("Selecting a no-delay animated node");
   yield selectNode(".animated", inspector);
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_shows_iterations.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_shows_iterations.js
@@ -1,20 +1,20 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the timeline-based UI is displays as many iteration elements as
-// there are iterations in an animation.
+// Check that the timeline is displays as many iteration elements as there are
+// iterations in an animation.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspectorNewUI();
+  let {inspector, panel} = yield openAnimationInspector();
 
   info("Selecting the test node");
   yield selectNode(".delayed", inspector);
 
   info("Getting the animation element from the panel");
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
   let animation = timelineEl.querySelector(".time-block");
   let iterations = animation.querySelector(".iterations");
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_shows_time_info.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_shows_time_info.js
@@ -1,20 +1,20 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the timeline-based UI displays animations' duration, delay and
-// iteration counts in tooltips.
+// Check that the timeline displays animations' duration, delay and iteration
+// counts in tooltips.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   info("Getting the animation element from the panel");
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
   let timeBlockNameEls = timelineEl.querySelectorAll(".time-block .name");
 
   // Verify that each time-block's name element has a tooltip that looks sort of
   // ok. We don't need to test the actual content.
   for (let el of timeBlockNameEls) {
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
@@ -9,17 +9,17 @@
 // Indeed, the header in the timeline UI always shows the unaltered time,
 // because there might be multiple animations displayed at the same time, some
 // of which may have a different rate than others. Those that have had their
 // rate changed have a delay = delay/rate and a duration = duration/rate.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_modify_playbackRate.html");
 
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
 
   let timeBlocks = timelineEl.querySelectorAll(".time-block");
   is(timeBlocks.length, 2, "2 animations are displayed");
 
   info("The first animation has its rate set to 1, let's measure it");
 
--- a/browser/devtools/animationinspector/test/browser_animation_timeline_ui.js
+++ b/browser/devtools/animationinspector/test/browser_animation_timeline_ui.js
@@ -1,19 +1,19 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the timeline-based UI contains the right elements.
+// Check that the timeline contains the right elements.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel} = yield openAnimationInspectorNewUI();
+  let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let el = timeline.rootWrapperEl;
 
   ok(el.querySelector(".time-header"),
      "The header element is in the DOM of the timeline");
   ok(el.querySelectorAll(".time-header .time-tick").length,
      "The header has some time graduations");
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_toggle_button_updates_playerWidgets.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that pressing the main toggle button also updates the displayed
-// player widgets.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Select an animated node");
-  yield selectNode(".animated", inspector);
-  let widget = panel.playerWidgets[0];
-  let player = widget.player;
-
-  info("Listen to animation state changes and click the toggle button to " +
-    "pause all animations");
-  let onPaused = waitForPlayState(player, "paused");
-  yield panel.toggleAll();
-  yield onPaused;
-
-  info("Checking the selected node's animation player widget's state");
-  is(player.state.playState, "paused", "The player front's state is paused");
-  ok(widget.el.classList.contains("paused"), "The widget's UI is in paused state");
-
-  info("Listen to animation state changes and click the toggle button to " +
-    "play all animations");
-  let onRunning = waitForPlayState(player, "running");
-  yield panel.toggleAll();
-  yield onRunning;
-
-  info("Checking the selected node's animation player widget's state again");
-  is(player.state.playState, "running", "The player front's state is running");
-  ok(widget.el.classList.contains("running"), "The widget's UI is in running state");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_toolbar_exists.js
+++ b/browser/devtools/animationinspector/test/browser_animation_toolbar_exists.js
@@ -1,26 +1,34 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the animation panel has a top toolbar that contains the play/pause
 // button and that is displayed at all times.
+// Also test that this toolbar gets replaced by the timeline toolbar when there
+// are animations to be displayed.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
   let {inspector, window} = yield openAnimationInspector();
   let doc = window.document;
+  let toolbar = doc.querySelector("#global-toolbar");
 
-  let toolbar = doc.querySelector("#toolbar");
-  ok(toolbar, "The panel contains the toolbar element");
-  ok(toolbar.querySelector("#toggle-all"), "The toolbar contains the toggle button");
-  ok(isNodeVisible(toolbar), "The toolbar is visible");
+  ok(toolbar, "The panel contains the toolbar element with the new UI");
+  ok(!isNodeVisible(toolbar),
+     "The toolbar is hidden while there are animations");
 
-  info("Select an animated node");
-  yield selectNode(".animated", inspector);
+  let timelineToolbar = doc.querySelector("#timeline-toolbar");
+  ok(timelineToolbar, "The panel contains a timeline toolbar element");
+  ok(isNodeVisible(timelineToolbar),
+     "The timeline toolbar is visible when there are animations");
 
-  toolbar = doc.querySelector("#toolbar");
-  ok(toolbar, "The panel still contains the toolbar element");
-  ok(isNodeVisible(toolbar), "The toolbar is still visible");
+  info("Select a node that has no animations");
+  yield selectNode(".still", inspector);
+
+  ok(isNodeVisible(toolbar),
+     "The toolbar is shown when there are no animations");
+  ok(!isNodeVisible(timelineToolbar),
+     "The timeline toolbar is hidden when there are no animations");
 });
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_changes.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Verify that if the animation object changes in content, then the widget
-// reflects that change.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {panel, inspector} = yield openAnimationInspector();
-
-  info("Select the test node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget");
-  let widget = panel.playerWidgets[0];
-  let player = widget.player;
-
-  info("Wait for paused playState");
-  let onPaused = waitForPlayState(player, "paused");
-
-  info("Pause the animation via the content DOM");
-  yield executeInContent("Test:ToggleAnimationPlayer", {
-    selector: ".animated",
-    animationIndex: 0,
-    pause: true
-  });
-
-  yield onPaused;
-
-  is(player.state.playState, "paused", "The AnimationPlayerFront is paused");
-  ok(widget.el.classList.contains("paused"), "The button's state has changed");
-  ok(!widget.rafID, "The smooth timeline animation has been stopped");
-
-  info("Wait for running playState");
-  let onRunning = waitForPlayState(player, "running");
-
-  info("Play the animation via the content DOM");
-  yield executeInContent("Test:ToggleAnimationPlayer", {
-    selector: ".animated",
-    animationIndex: 0,
-    pause: false
-  });
-
-  yield onRunning;
-
-  is(player.state.playState, "running", "The AnimationPlayerFront is running");
-  ok(widget.el.classList.contains("running"), "The button's state has changed");
-  ok(widget.rafID, "The smooth timeline animation has been started");
-});
--- a/browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_data_changes.js
+++ b/browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_data_changes.js
@@ -7,70 +7,50 @@
 // Verify that if the animation's duration, iterations or delay change in
 // content, then the widget reflects the changes.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let ui = yield openAnimationInspector();
   yield testDataUpdates(ui);
-
-  info("Close the toolbox, reload the tab, and try again with the new UI");
-  ui = yield closeAnimationInspectorAndRestartWithNewUI(true);
-  yield testDataUpdates(ui, true);
 });
 
-function* testDataUpdates({panel, controller, inspector}, isNewUI=false) {
+function* testDataUpdates({panel, controller, inspector}) {
   info("Select the test node");
   yield selectNode(".animated", inspector);
 
   let animation = controller.animationPlayers[0];
-  yield setStyle(animation, panel, "animationDuration", "5.5s", isNewUI);
-  yield setStyle(animation, panel, "animationIterationCount", "300", isNewUI);
-  yield setStyle(animation, panel, "animationDelay", "45s", isNewUI);
+  yield setStyle(animation, panel, "animationDuration", "5.5s");
+  yield setStyle(animation, panel, "animationIterationCount", "300");
+  yield setStyle(animation, panel, "animationDelay", "45s");
 
-  if (isNewUI) {
-    let animationsEl = panel.animationsTimelineComponent.animationsEl;
-    let timeBlockEl = animationsEl.querySelector(".time-block");
-
-    // 45s delay + (300 * 5.5)s duration
-    let expectedTotalDuration = 1695 * 1000;
-    let timeRatio = expectedTotalDuration / timeBlockEl.offsetWidth;
+  let animationsEl = panel.animationsTimelineComponent.animationsEl;
+  let timeBlockEl = animationsEl.querySelector(".time-block");
 
-    // XXX: the nb and size of each iteration cannot be tested easily (displayed
-    // using a linear-gradient background and capped at 2px wide). They should
-    // be tested in bug 1173761.
-    let delayWidth = parseFloat(timeBlockEl.querySelector(".delay").style.width);
-    is(Math.round(delayWidth * timeRatio), 45 * 1000,
-      "The timeline has the right delay");
-  } else {
-    let widget = panel.playerWidgets[0];
-    is(widget.metaDataComponent.durationValue.textContent, "5.50s",
-      "The widget shows the new duration");
-    is(widget.metaDataComponent.iterationValue.textContent, "300",
-      "The widget shows the new iteration count");
-    is(widget.metaDataComponent.delayValue.textContent, "45s",
-      "The widget shows the new delay");
-  }
+  // 45s delay + (300 * 5.5)s duration
+  let expectedTotalDuration = 1695 * 1000;
+  let timeRatio = expectedTotalDuration / timeBlockEl.offsetWidth;
+
+  // XXX: the nb and size of each iteration cannot be tested easily (displayed
+  // using a linear-gradient background and capped at 2px wide). They should
+  // be tested in bug 1173761.
+  let delayWidth = parseFloat(timeBlockEl.querySelector(".delay").style.width);
+  is(Math.round(delayWidth * timeRatio), 45 * 1000,
+    "The timeline has the right delay");
 }
 
-function* setStyle(animation, panel, name, value, isNewUI=false) {
+function* setStyle(animation, panel, name, value) {
   info("Change the animation style via the content DOM. Setting " +
     name + " to " + value);
 
   let onAnimationChanged = once(animation, "changed");
   yield executeInContent("devtools:test:setStyle", {
     selector: ".animated",
     propertyName: name,
     propertyValue: value
   });
   yield onAnimationChanged;
 
   // Also wait for the target node previews to be loaded if the panel got
   // refreshed as a result of this animation mutation.
   yield waitForAllAnimationTargets(panel);
-
-  // If this is the playerWidget-based UI, wait for the auto-refresh event too
-  // to make sure the UI has updated.
-  if (!isNewUI) {
-    yield once(animation, animation.AUTO_REFRESH_EVENT);
-  }
 }
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_rate_changes.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that setting an animation's playbackRate via the WebAnimations API (from
-// content), actually changes the rate in the corresponding widget too.
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Selecting the test node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget");
-  let widget = panel.playerWidgets[0];
-
-  info("Change the animation's playbackRate via the content DOM");
-  let onRateChanged = waitForStateCondition(widget.player, state => {
-    return state.playbackRate === 2;
-  }, "playbackRate === 2");
-  yield executeInContent("Test:SetAnimationPlayerPlaybackRate", {
-    selector: ".animated",
-    animationIndex: 0,
-    playbackRate: 2
-  });
-  yield onRateChanged;
-
-  is(widget.rateComponent.el.value, "2",
-    "The playbackRate select value was changed");
-
-  info("Change the animation's playbackRate again via the content DOM");
-  onRateChanged = waitForStateCondition(widget.player, state => {
-    return state.playbackRate === 0.3;
-  }, "playbackRate === 0.3");
-  yield executeInContent("Test:SetAnimationPlayerPlaybackRate", {
-    selector: ".animated",
-    animationIndex: 0,
-    playbackRate: 0.3
-  });
-  yield onRateChanged;
-
-  is(widget.rateComponent.el.value, "0.3",
-    "The playbackRate select value was changed again");
-});
deleted file mode 100644
--- a/browser/devtools/animationinspector/test/browser_animation_ui_updates_when_animation_time_changes.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that setting an animation's currentTime via the WebAnimations API (from
-// content), actually changes the time in the corresponding widget too.
-
-var L10N = new ViewHelpers.L10N();
-
-add_task(function*() {
-  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
-  let {inspector, panel} = yield openAnimationInspector();
-
-  info("Selecting the test node");
-  yield selectNode(".animated", inspector);
-
-  info("Get the player widget");
-  let widget = panel.playerWidgets[0];
-
-  info("Pause the player so we can compare times easily");
-  yield executeInContent("Test:ToggleAnimationPlayer", {
-    selector: ".animated",
-    animationIndex: 0,
-    pause: true
-  });
-  yield onceNextPlayerRefresh(widget.player);
-
-  ok(widget.el.classList.contains("paused"), "The widget is in pause mode");
-
-  info("Change the animation's currentTime via the content DOM");
-  yield executeInContent("Test:SetAnimationPlayerCurrentTime", {
-    selector: ".animated",
-    animationIndex: 0,
-    currentTime: 0
-  });
-  yield onceNextPlayerRefresh(widget.player);
-
-  is(widget.currentTimeEl.value, 0, "The currentTime slider's value was changed");
-
-  info("Change the animation's currentTime again via the content DOM");
-  yield executeInContent("Test:SetAnimationPlayerCurrentTime", {
-    selector: ".animated",
-    animationIndex: 0,
-    currentTime: 300
-  });
-  yield onceNextPlayerRefresh(widget.player);
-
-  is(widget.currentTimeEl.value, 300,
-    "The currentTime slider's value was changed again");
-});
--- a/browser/devtools/animationinspector/test/head.js
+++ b/browser/devtools/animationinspector/test/head.js
@@ -79,23 +79,16 @@ function addTab(url) {
 
     def.resolve(tab);
   }, true);
 
   return def.promise;
 }
 
 /**
- * Switch ON the new UI pref.
- */
-function enableNewUI() {
-  Services.prefs.setBoolPref(NEW_UI_PREF, true);
-}
-
-/**
  * Reload the current tab location.
  */
 function reloadTab() {
   return executeInContent("devtools:test:reload", {}, {}, false);
 }
 
 /**
  * Get the NodeFront for a given css selector, via the protocol
@@ -142,25 +135,19 @@ var selectNode = Task.async(function*(da
 /**
  * Check if there are the expected number of animations being displayed in the
  * panel right now.
  * @param {AnimationsPanel} panel
  * @param {Number} nbAnimations The expected number of animations.
  * @param {String} msg An optional string to be used as the assertion message.
  */
 function assertAnimationsDisplayed(panel, nbAnimations, msg="") {
-  let isNewUI = Services.prefs.getBoolPref(NEW_UI_PREF);
   msg = msg || `There are ${nbAnimations} animations in the panel`;
-  if (isNewUI) {
-    is(panel.animationsTimelineComponent.animationsEl.childNodes.length,
-       nbAnimations, msg);
-  } else {
-    is(panel.playersEl.querySelectorAll(".player-widget").length,
-       nbAnimations, msg);
-  }
+  is(panel.animationsTimelineComponent.animationsEl.childNodes.length,
+     nbAnimations, msg);
 }
 
 /**
  * Takes an Inspector panel that was just created, and waits
  * for a "inspector-updated" event as well as the animation inspector
  * sidebar to be ready. Returns a promise once these are completed.
  *
  * @param {InspectorPanel} inspector
@@ -225,53 +212,25 @@ var openAnimationInspector = Task.async(
     inspector: inspector,
     controller: AnimationsController,
     panel: AnimationsPanel,
     window: win
   };
 });
 
 /**
- * Turn on the new timeline-based UI pref ON, and then open the toolbox, with
- * the inspector tool visible and the animationinspector sidebar selected.
- * @return a promise that resolves when the inspector is ready.
- */
-function openAnimationInspectorNewUI() {
-  enableNewUI();
-  return openAnimationInspector();
-}
-
-/**
  * Close the toolbox.
  * @return a promise that resolves when the toolbox has closed.
  */
 var closeAnimationInspector = Task.async(function*() {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   yield gDevTools.closeToolbox(target);
 });
 
 /**
- * During the time period we migrate from the playerWidgets-based UI to the new
- * AnimationTimeline UI, we'll want to run certain tests against both UI.
- * This closes the toolbox, switch the new UI pref ON, and opens the toolbox
- * again, with the animation inspector panel selected.
- * @param {Boolean} reload Optionally reload the page after the toolbox was
- * closed and before it is opened again.
- * @return a promise that resolves when the animation inspector is ready.
- */
-var closeAnimationInspectorAndRestartWithNewUI = Task.async(function*(reload) {
-  info("Close the toolbox and test again with the new UI");
-  yield closeAnimationInspector();
-  if (reload) {
-    yield reloadTab();
-  }
-  return yield openAnimationInspectorNewUI();
-});
-
-/**
  * Wait for the toolbox frame to receive focus after it loads
  * @param {Toolbox} toolbox
  * @return a promise that resolves when focus has been received
  */
 function waitForToolboxFrameFocus(toolbox) {
   info("Making sure that the toolbox's frame is focused");
   let def = promise.defer();
   let win = toolbox.frame.contentWindow;
@@ -476,23 +435,17 @@ function isNodeVisible(node) {
 
 /**
  * Wait for all AnimationTargetNode instances to be fully loaded
  * (fetched their related actor and rendered), and return them.
  * @param {AnimationsPanel} panel
  * @return {Array} all AnimationTargetNode instances
  */
 var waitForAllAnimationTargets = Task.async(function*(panel) {
-  let targets = [];
-  if (panel.animationsTimelineComponent) {
-    targets = targets.concat(panel.animationsTimelineComponent.targetNodes);
-  }
-  if (panel.playerWidgets) {
-    targets = targets.concat(panel.playerWidgets.map(w => w.targetNodeComponent));
-  }
+  let targets = panel.animationsTimelineComponent.targetNodes;
   yield promise.all(targets.map(t => {
     if (!t.nodeFront) {
       return t.once("target-retrieved");
     }
     return false;
   }));
   return targets;
 });
--- a/browser/devtools/animationinspector/test/unit/test_timeScale.js
+++ b/browser/devtools/animationinspector/test/unit/test_timeScale.js
@@ -7,51 +7,51 @@
 
 const Cu = Components.utils;
 const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {TimeScale} = require("devtools/animationinspector/components");
 
 const TEST_ANIMATIONS = [{
   desc: "Testing a few standard animations",
   animations: [{
-    startTime: 500,
+    previousStartTime: 500,
     delay: 0,
     duration: 1000,
     iterationCount: 1,
     playbackRate: 1
   }, {
-    startTime: 400,
+    previousStartTime: 400,
     delay: 100,
     duration: 10,
     iterationCount: 100,
     playbackRate: 1
   }, {
-    startTime: 50,
+    previousStartTime: 50,
     delay: 1000,
     duration: 100,
     iterationCount: 20,
     playbackRate: 1
   }],
   expectedMinStart: 50,
   expectedMaxEnd: 3050
 }, {
   desc: "Testing a single negative-delay animation",
   animations: [{
-    startTime: 100,
+    previousStartTime: 100,
     delay: -100,
     duration: 100,
     iterationCount: 1,
     playbackRate: 1
   }],
   expectedMinStart: 0,
   expectedMaxEnd: 100
 }, {
   desc: "Testing a single negative-delay animation with a different rate",
   animations: [{
-    startTime: 3500,
+    previousStartTime: 3500,
     delay: -1000,
     duration: 10000,
     iterationCount: 2,
     playbackRate: 2
   }],
   expectedMinStart: 3000,
   expectedMaxEnd: 13000
 }];
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -41,16 +41,17 @@ function SelectorSearch(aInspector, aInp
   this._searchSuggestions = {};
   this._searchIndex = 0;
 
   // bind!
   this._showPopup = this._showPopup.bind(this);
   this._onHTMLSearch = this._onHTMLSearch.bind(this);
   this._onSearchKeypress = this._onSearchKeypress.bind(this);
   this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
+  this._onMarkupMutation = this._onMarkupMutation.bind(this);
 
   // Options for the AutocompletePopup.
   let options = {
     panelId: "inspector-searchbox-panel",
     listBoxId: "searchbox-panel-listbox",
     autoSelect: true,
     position: "before_start",
     direction: "ltr",
@@ -58,16 +59,17 @@ function SelectorSearch(aInspector, aInp
     onClick: this._onListBoxKeypress,
     onKeypress: this._onListBoxKeypress
   };
   this.searchPopup = new AutocompletePopup(this.panelDoc, options);
 
   // event listeners.
   this.searchBox.addEventListener("command", this._onHTMLSearch, true);
   this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
+  this.inspector.on("markupmutation", this._onMarkupMutation);
 
   // For testing, we need to be able to wait for the most recent node request
   // to finish.  Tests can watch this promise for that.
   this._lastQuery = promise.resolve(null);
   EventEmitter.decorate(this);
 }
 
 exports.SelectorSearch = SelectorSearch;
@@ -165,16 +167,17 @@ SelectorSearch.prototype = {
 
   /**
    * Removes event listeners and cleans up references.
    */
   destroy: function() {
     // event listeners.
     this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
     this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
+    this.inspector.off("markupmutation", this._onMarkupMutation);
     this.searchPopup.destroy();
     this.searchPopup = null;
     this.searchBox = null;
     this.panelDoc = null;
     this._searchResults = null;
     this._searchSuggestions = null;
   },
 
@@ -419,16 +422,25 @@ SelectorSearch.prototype = {
                                  ["",""])[1];
         this._onHTMLSearch();
         break;
     }
     this.emit("processing-done");
   },
 
   /**
+   * Reset previous search results on markup-mutations to make sure we search
+   * again after nodes have been added/removed/changed.
+   */
+  _onMarkupMutation: function() {
+    this._searchResults = null;
+    this._lastSearched = null;
+  },
+
+  /**
    * Populates the suggestions list and show the suggestion popup.
    */
   _showPopup: function(aList, aFirstPart, aState) {
     let total = 0;
     let query = this.searchBox.value;
     let items = [];
 
     for (let [value, count, state] of aList) {
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -97,14 +97,15 @@ skip-if = e10s # GCLI isn't e10s compati
 [browser_inspector_remove-iframe-during-load.js]
 [browser_inspector_scrolling.js]
 skip-if = e10s # Test synthesize scrolling events in content. Also, see bug 1035661.
 [browser_inspector_search-01.js]
 [browser_inspector_search-02.js]
 [browser_inspector_search-03.js]
 [browser_inspector_search-04.js]
 [browser_inspector_search-05.js]
+[browser_inspector_search-06.js]
 [browser_inspector_search-reserved.js]
 [browser_inspector_select-docshell.js]
 [browser_inspector_select-last-selected.js]
 [browser_inspector_search-navigation.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_switch-to-inspector-on-pick.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_search-06.js
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Check that searching again for nodes after they are removed or added from the
+// DOM works correctly.
+
+const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html";
+
+add_task(function* () {
+  let { inspector, testActor } = yield openInspectorForURL(TEST_URL);
+
+  info("Searching for test node #d1");
+  yield focusSearchBoxUsingShortcut(inspector.panelWin);
+  yield synthesizeKeys(["#", "d", "1"], inspector);
+
+  assertHasResult(inspector, true);
+
+  info("Removing node #d1");
+  yield mutatePage(inspector, testActor,
+                   "document.getElementById(\"d1\").remove()");
+
+  info("Pressing return button to search again for node #d1.");
+  yield synthesizeKeys("VK_RETURN", inspector);
+
+  assertHasResult(inspector, false);
+
+  info("Emptying the field and searching for a node that doesn't exist: #d3");
+  let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3"];
+  yield synthesizeKeys(keys, inspector);
+
+  assertHasResult(inspector, false);
+
+  info("Create the #d3 node in the page");
+  yield mutatePage(inspector, testActor,
+                   `document.getElementById("d2").insertAdjacentHTML(
+                    "afterend", "<div id=d3></div>")`);
+
+  info("Pressing return button to search again for node #d3.");
+  yield synthesizeKeys("VK_RETURN", inspector);
+
+  assertHasResult(inspector, true);
+
+  // Catch-all event for remaining server requests when searching for the new
+  // node.
+  yield inspector.once("inspector-updated");
+});
+
+function* synthesizeKeys(keys, inspector) {
+  if (typeof keys === "string") {
+    keys = [keys];
+  }
+
+  for (let key of keys) {
+    info("Synthesizing key " + key + " in the search box");
+    let eventHandled = once(inspector.searchBox, "keypress", true);
+    EventUtils.synthesizeKey(key, {}, inspector.panelWin);
+    yield eventHandled;
+    info("Waiting for the search query to complete");
+    yield inspector.searchSuggestions._lastQuery;
+  }
+}
+
+function assertHasResult(inspector, expectResult) {
+  is(inspector.searchBox.classList.contains("devtools-no-search-result"),
+     !expectResult,
+     "There are" + (expectResult ? "" : " no") + " search results");
+}
+
+function* mutatePage(inspector, testActor, expression) {
+  let onUpdated = inspector.once("inspector-updated");
+  yield testActor.eval(expression);
+  yield onUpdated;
+}
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -80,16 +80,18 @@ var UI = {
 
       // TODO: Remove if/when dropdown layout is removed.
       let toolbarNode = document.querySelector("#main-toolbar");
       toolbarNode.classList.add("sidebar-layout");
       let projectNode = document.querySelector("#project-panel-button");
       projectNode.setAttribute("hidden", "true");
       let runtimeNode = document.querySelector("#runtime-panel-button");
       runtimeNode.setAttribute("hidden", "true");
+      let openAppNode = document.querySelector("#menuitem-show_projectPanel");
+      openAppNode.setAttribute("hidden", "true");
     }
     runtimeList = new RuntimeList(window, window);
     if (runtimeList.sidebarsEnabled) {
       Cmds.showRuntimePanel();
     } else {
       runtimeList.update();
     }
 
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -62,17 +62,17 @@
   </commandset>
 
   <menubar id="main-menubar">
     <menu id="menu-project" label="&projectMenu_label;" accesskey="&projectMenu_accesskey;">
       <menupopup id="menu-project-popup">
         <menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
         <menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
         <menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
-        <menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
+        <menuitem id="menuitem-show_projectPanel" command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
         <menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>
         <menuitem command="cmd_toggleToolbox" key="key_toggleToolbox" label="&projectMenu_debug_label;" accesskey="&projectMenu_debug_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_removeProject" accesskey="&projectMenu_remove_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_showPrefs" label="&projectMenu_showPrefs_label;" accesskey="&projectMenu_showPrefs_accesskey;"/>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -701,25 +701,27 @@ you can use these alternative items. Oth
 <!ENTITY editBookmark.cancel.label                   "Cancel">
 <!ENTITY editBookmark.removeBookmark.accessKey       "R">
 
 <!ENTITY identity.connectionSecure "Secure Connection">
 <!ENTITY identity.connectionNotSecure "Connection is Not Secure">
 <!ENTITY identity.connectionFile "This page is stored on your computer.">
 <!ENTITY identity.connectionVerified1 "You are securely connected to this site, run by:">
 <!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
+<!ENTITY identity.insecureLoginForms "Your login could be compromised.">
 
 <!-- Strings for connection state warnings. -->
 <!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
 <!ENTITY identity.passiveLoaded "Parts of this page are not secure (such as images).">
 <!ENTITY identity.activeLoaded "You have disabled protection on this page.">
 <!ENTITY identity.weakEncryption "This page uses weak encryption.">
 
 <!-- Strings for connection state warnings in the subview. -->
 <!ENTITY identity.description.insecure "Your connection to this site is not private. Information you submit could be viewed by others (like passwords, messages, credit cards, etc.).">
+<!ENTITY identity.description.insecureLoginForms "The login information you enter on this page is not secure and could be compromised.">
 <!ENTITY identity.description.weakCipher "Your connection to this website uses weak encryption and is not private.">
 <!ENTITY identity.description.weakCipher2 "Other people can view your information or modify the website's behavior.">
 <!ENTITY identity.description.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
 <!ENTITY identity.description.passiveLoaded "Your connection is not private and information you share with the site could be viewed by others.">
 <!ENTITY identity.description.passiveLoaded2 "This website contains content that is not secure (such as images).">
 <!ENTITY identity.description.passiveLoaded3 "Although &brandShortName; has blocked some content, there is still content on the page that is not secure (such as images).">
 <!ENTITY identity.description.activeLoaded "This website contains content that is not secure (such as scripts) and your connection to it is not private.">
 <!ENTITY identity.description.activeLoaded2 "Information you share with this site could be viewed by others (like passwords, messages, credit cards, etc.).">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/blocklists.dtd
@@ -0,0 +1,14 @@
+<!-- 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/. -->
+
+<!ENTITY window.title                 "Block Lists">
+<!ENTITY window.width                 "50em">
+
+<!ENTITY treehead.list.label          "List">
+<!ENTITY windowClose.key              "w">
+
+<!ENTITY button.cancel.label          "Cancel">
+<!ENTITY button.cancel.accesskey      "C">
+<!ENTITY button.ok.label              "Save Changes">
+<!ENTITY button.ok.accesskey          "S">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -23,16 +23,38 @@ cookiepermissionstext=You can specify wh
 cookiepermissionstitle=Exceptions - Cookies
 addonspermissionstext=You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
 addons_permissions_title=Allowed Sites - Add-ons Installation
 popuppermissionstext=You can specify which websites are allowed to open pop-up windows. Type the exact address of the site you want to allow and then click Allow.
 popuppermissionstitle=Allowed Sites - Pop-ups
 invalidURI=Please enter a valid hostname
 invalidURITitle=Invalid Hostname Entered
 
+#### Block List Manager
+
+blockliststext=You can choose which list Firefox will use to block Web elements that may track your browsing activity.
+blockliststitle=Block Lists
+# LOCALIZATION NOTE (mozNameTemplate): This template constructs the name of the
+# block list in the block lists dialog. It combines the list name and
+# description.
+#   e.g. mozNameTemplate : "Standard (Recommended). This list does a pretty good job."
+#   %1$S = list name (fooName), %2$S = list descriptive text (fooDesc)
+mozNameTemplate=%1$S %2$S
+# LOCALIZATION NOTE (mozstdName, etc.): These labels appear in the tracking
+# protection block lists dialog, mozNameTemplate is used to create the final
+# string. Note that in the future these two strings (name, desc) could be
+# displayed on two different lines.
+mozstdName=Disconnect.me basic protection (Recommended).
+mozstdDesc=Allows some trackers so websites function properly.
+mozfullName=Disconnect.me strict protection.
+mozfullDesc=Blocks known trackers. Some sites may not function properly.
+# LOCALIZATION NOTE (blocklistChangeRequiresRestart, restartTitle): %S = brandShortName
+blocklistChangeRequiresRestart=%S must restart to change block lists.
+shouldRestartTitle=Restart %S
+
 #### Master Password
 
 pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
 pw_change_failed_title=Password Change Failed
 
 #### Fonts
 
 # LOCALIZATION NOTE: Next two strings are for language name representations with
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -8,16 +8,18 @@
 <!ENTITY dntTrackingNotOkay4.accesskey "n">
 <!ENTITY doNotTrackInfo.label          "Learn More">
 <!ENTITY trackingProtection5.label     "Use Tracking Protection">
 <!ENTITY trackingProtection5.accesskey "i">
 <!ENTITY trackingProtectionLearnMore.label "Learn more">
 <!ENTITY trackingProtectionPBM5.label         "Use Tracking Protection in Private Windows">
 <!ENTITY trackingProtectionPBM5.accesskey     "v">
 <!ENTITY trackingProtectionPBMLearnMore.label "Learn more">
+<!ENTITY changeBlockList.label          "Change Block List">
+<!ENTITY changeBlockList.accesskey      "C">
 
 <!ENTITY  history.label                 "History">
 
 <!ENTITY  locationBar.label             "Location Bar">
 
 <!ENTITY  locbar.suggest.label          "When using the location bar, suggest:">
 <!ENTITY  locbar.history.label          "History">
 <!ENTITY  locbar.history.accesskey      "H">
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -117,16 +117,17 @@
     locale/browser/feeds/subscribe.properties       (%chrome/browser/feeds/subscribe.properties)
     locale/browser/migration/migration.dtd         (%chrome/browser/migration/migration.dtd)
     locale/browser/migration/migration.properties  (%chrome/browser/migration/migration.properties)
     locale/browser/preferences/aboutPermissions.dtd          (%chrome/browser/preferences/aboutPermissions.dtd)
     locale/browser/preferences/aboutPermissions.properties   (%chrome/browser/preferences/aboutPermissions.properties)
     locale/browser/preferences/advanced.dtd           (%chrome/browser/preferences/advanced.dtd)
     locale/browser/preferences/applicationManager.dtd        (%chrome/browser/preferences/applicationManager.dtd)
     locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
+    locale/browser/preferences/blocklists.dtd         (%chrome/browser/preferences/blocklists.dtd)
     locale/browser/preferences/colors.dtd             (%chrome/browser/preferences/colors.dtd)
     locale/browser/preferences/cookies.dtd            (%chrome/browser/preferences/cookies.dtd)
     locale/browser/preferences/content.dtd            (%chrome/browser/preferences/content.dtd)
     locale/browser/preferences/connection.dtd         (%chrome/browser/preferences/connection.dtd)
     locale/browser/preferences/applications.dtd       (%chrome/browser/preferences/applications.dtd)
     locale/browser/preferences/fonts.dtd              (%chrome/browser/preferences/fonts.dtd)
     locale/browser/preferences/main.dtd               (%chrome/browser/preferences/main.dtd)
     locale/browser/preferences/languages.dtd          (%chrome/browser/preferences/languages.dtd)
--- a/browser/themes/shared/devtools/animationinspector.css
+++ b/browser/themes/shared/devtools/animationinspector.css
@@ -30,109 +30,134 @@ body {
   padding: 0;
   display : flex;
   flex-direction: column;
   height: 100%;
   overflow: hidden;
   color: var(--theme-content-color3);
 }
 
-/* The top toolbar, containing the toggle-all button */
+/* The top toolbar, containing the toggle-all button. And the timeline toolbar,
+   containing playback control buttons, shown only when there are animations
+   displayed in the timeline */
 
-#toolbar {
+#global-toolbar,
+#timeline-toolbar {
   border-bottom: 1px solid var(--theme-splitter-color);
   display: flex;
   flex-direction: row;
   align-items: center;
   justify-content: flex-end;
   height: var(--toolbar-height);
 }
 
-#toolbar .label {
-  padding: 1px 4px;
+#timeline-toolbar {
+  display: none;
 }
 
-#toggle-all {
-  border-width: 0 0 0 1px;
-  min-height: var(--toolbar-height);
+[timeline] #global-toolbar {
+  display: none;
+}
+
+[timeline] #timeline-toolbar {
+  display: flex;
+}
+
+#global-toolbar .label {
+  padding: 1px 4px;
 }
 
 /* The main animations container */
 
 #players {
   height: calc(100% - var(--toolbar-height));
   overflow: auto;
 }
 
+[empty] #players {
+  display: none;
+}
+
 /* The error message, shown when an invalid/unanimated element is selected */
 
 #error-message {
   padding-top: 10%;
   text-align: center;
   flex: 1;
   overflow: auto;
 
   /* The error message is hidden by default */
   display: none;
 }
 
+[empty] #error-message {
+  display: block;
+}
 
-/* Element picker and toggle-all buttons */
+/* Element picker, toggle-all buttons, timeline pause button, ... */
 
-#element-picker,
-#toggle-all {
+#global-toolbar .devtools-button,
+#timeline-toolbar .devtools-button {
+  border-width: 0 0 0 1px;
+  min-height: var(--toolbar-height);
+}
+
+.devtools-button {
   position: relative;
 }
 
-#element-picker::before,
-#toggle-all::before {
+.devtools-button::before {
   content: "";
   display: block;
   width: 16px;
   height: 16px;
   position: absolute;
   left: 50%;
   top: 50%;
   margin: -8px 0 0 -8px;
+}
+
+#element-picker::before {
   background-image: url("chrome://browser/skin/devtools/command-pick.png");
 }
 
-#toggle-all::before {
+.pause-button::before {
   background-image: url("debugger-pause.png");
 }
 
 #element-picker[checked]::before {
   background-position: -48px 0;
   filter: none; /* Icon is blue when checked, don't invert for light theme */
 }
 
-#toggle-all.paused::before {
+.pause-button.paused::before {
   background-image: url("debugger-play.png");
 }
 
 @media (min-resolution: 1.1dppx) {
   #element-picker::before {
     background-image: url("chrome://browser/skin/devtools/command-pick@2x.png");
     background-size: 64px;
   }
 
-  #toggle-all::before {
+  .pause-button::before {
     background-image: url("debugger-pause@2x.png");
   }
 
-  #toggle-all.paused::before {
+  .pause-button.paused::before {
     background-image: url("debugger-play@2x.png");
   }
 }
 
 /* Animation timeline component */
 
 .animation-timeline {
   height: 100%;
   overflow: hidden;
+  position: relative;
   /* The timeline gets its background-image from a canvas element created in
      /browser/devtools/animationinspector/utils.js drawGraphElementBackground
      thanks to document.mozSetImageElement("time-graduations", canvas)
      This is done so that the background can be built dynamically from script */
   background-image: -moz-element(#time-graduations);
   background-repeat: repeat-y;
   /* The animations are drawn 150px from the left edge so that animated nodes
      can be displayed in a sidebar */
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -17,16 +17,37 @@
 }
 
 button,
 treecol {
   /* override the * rule */
   -moz-user-select: none;
 }
 
+#engineList treechildren::-moz-tree-image(engineShown, checked),
+#blocklistsTree treechildren::-moz-tree-image(selectionCol, checked) {
+  list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+  width: 21px;
+  height: 21px;
+}
+
+#engineList treechildren::-moz-tree-image(engineShown, checked, selected),
+#blocklistsTree treechildren::-moz-tree-image(selectionCol, checked, selected) {
+  list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+#engineList treechildren::-moz-tree-row,
+#blocklistsTree treechildren::-moz-tree-row {
+  min-height: 36px;
+}
+
+#selectionCol {
+  min-width: 26px;
+}
+
 /* Category List */
 
 #categories {
   max-height: 100vh;
 }
 
 #categories > scrollbox {
   overflow-x: hidden !important;
--- a/browser/themes/shared/incontentprefs/search.css
+++ b/browser/themes/shared/incontentprefs/search.css
@@ -14,37 +14,23 @@
 .searchengine-menuitem > .menu-iconic-left {
   display: -moz-box;
 }
 
 #engineList {
   margin: .5em 0;
 }
 
-#engineList treechildren::-moz-tree-image(engineShown, checked) {
-  list-style-image: url("chrome://global/skin/in-content/check.svg#check");
-  width: 21px;
-  height: 21px;
-}
-
-#engineList treechildren::-moz-tree-image(engineShown, checked, selected) {
-  list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
-}
-
 #engineList treechildren::-moz-tree-image(engineName) {
   -moz-margin-end: 10px;
   -moz-margin-start: 1px;
   width: 16px;
   height: 16px;
 }
 
-#engineList treechildren::-moz-tree-row {
-  min-height: 36px;
-}
-
 #engineList treechildren::-moz-tree-drop-feedback {
   background-color: Highlight;
   width: 10000px; /* 100% doesn't work; 10k is hopefully larger than any window
                      we may have, overflow isn't visible. */
   height: 2px;
   -moz-margin-start: 0;
 }
 
--- a/mobile/android/base/home/ReadingListRow.java
+++ b/mobile/android/base/home/ReadingListRow.java
@@ -1,62 +1,45 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.home.TwoLinePageRow;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class ReadingListRow extends LinearLayout {
-
-    private final Resources resources;
-
-    private final TextView title;
-    private final TextView excerpt;
-    private final TextView readTime;
-    private final ImageView indicator;
-
-    // Average reading speed in words per minute.
-    private static final int AVERAGE_READING_SPEED = 250;
-
-    // Length of average word.
-    private static final float AVERAGE_WORD_LENGTH = 5.1f;
-
+    private TextView title;
+    private TextView excerpt;
+    private ImageView indicator;
 
     public ReadingListRow(Context context) {
         this(context, null);
     }
 
     public ReadingListRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-
-        LayoutInflater.from(context).inflate(R.layout.reading_list_row_view, this);
+    }
 
-        resources = context.getResources();
-
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
         title = (TextView) findViewById(R.id.title);
         excerpt = (TextView) findViewById(R.id.excerpt);
-        readTime = (TextView) findViewById(R.id.read_time);
         indicator = (ImageView) findViewById(R.id.indicator);
     }
 
     public void updateFromCursor(Cursor cursor) {
         if (cursor == null) {
             return;
         }
 
@@ -68,33 +51,11 @@ public class ReadingListRow extends Line
         title.setText(TextUtils.isEmpty(titleText) ? StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url)) : titleText);
         title.setTextAppearance(getContext(), isUnread ? R.style.Widget_ReadingListRow_Title_Unread : R.style.Widget_ReadingListRow_Title_Read);
 
         final String excerptText = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.EXCERPT));
         excerpt.setText(TextUtils.isEmpty(excerptText) ? url : excerptText);
         excerpt.setTextAppearance(getContext(), isUnread ? R.style.Widget_ReadingListRow_Title_Unread : R.style.Widget_ReadingListRow_Title_Read);
 
         indicator.setImageResource(isUnread ? R.drawable.reading_list_indicator_unread : R.drawable.reading_list_indicator_read);
-
-        /* Disabled until UX issues are fixed (see bug 1110461).
-        final int lengthIndex = cursor.getColumnIndexOrThrow(ReadingListItems.LENGTH);
-        final int minutes = getEstimatedReadTime(cursor.getInt(lengthIndex));
-        if (minutes <= 60) {
-            readTime.setText(resources.getString(R.string.reading_list_time_minutes, minutes));
-        } else {
-            readTime.setText(resources.getString(R.string.reading_list_time_over_an_hour));
-        }
-        */
     }
 
-    /**
-     * Calculates the estimated time to read an article based on its length.
-     *
-     * @param length of the article (in characters)
-     * @return estimated time to read the article (in minutes)
-     */
-    private static int getEstimatedReadTime(int length) {
-        final int minutes = (int) Math.ceil((length / AVERAGE_WORD_LENGTH) / AVERAGE_READING_SPEED);
-
-        // Minimum of one minute.
-        return Math.max(minutes, 1);
-    }
 }
--- a/mobile/android/base/home/TopSitesPanel.java
+++ b/mobile/android/base/home/TopSitesPanel.java
@@ -337,16 +337,21 @@ public class TopSitesPanel extends HomeF
         if (menuInfo == null) {
             return;
         }
 
         if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
             // Long pressed item was not a Top Sites GridView item. Superclass
             // can handle this.
             super.onCreateContextMenu(menu, view, menuInfo);
+
+            if (!RestrictedProfiles.isAllowed(view.getContext(), Restriction.DISALLOW_CLEAR_HISTORY)) {
+                menu.findItem(R.id.home_remove).setVisible(false);
+            }
+
             return;
         }
 
         final Context context = view.getContext();
 
         // Long pressed item was a Top Sites GridView item, handle it.
         MenuInflater inflater = new MenuInflater(context);
         inflater.inflate(R.menu.home_contextmenu, menu);
--- a/mobile/android/base/resources/layout/reading_list_item_row.xml
+++ b/mobile/android/base/resources/layout/reading_list_item_row.xml
@@ -1,9 +1,36 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <org.mozilla.gecko.home.ReadingListRow xmlns:android="http://schemas.android.com/apk/res/android"
                                        android:layout_width="match_parent"
                                        android:layout_height="@dimen/reading_list_row_height"
-                                       android:layout_gravity="center_vertical"/>
+                                       android:layout_gravity="center_vertical">
+
+    <ImageView android:id="@+id/indicator"
+               android:layout_width="64dp"
+               android:layout_height="match_parent"
+               android:scaleType="center" />
+
+    <LinearLayout android:layout_width="0dip"
+                  android:layout_height="match_parent"
+                  android:layout_weight="1"
+                  android:paddingRight="@dimen/reading_list_row_padding_right"
+                  android:orientation="vertical"
+                  android:gravity="center_vertical">
+
+        <TextView android:id="@+id/title"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:paddingBottom="4dp"
+                  style="@style/Widget.ReadingListRow.Title" />
+
+        <TextView android:id="@+id/excerpt"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  style="@style/Widget.ReadingListRow.Description" />
+
+    </LinearLayout>
+
+</org.mozilla.gecko.home.ReadingListRow>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/reading_list_row_view.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <ImageView
-        android:id="@+id/indicator"
-        android:layout_width="64dp"
-        android:layout_height="match_parent"
-        android:scaleType="center" />
-
-    <LinearLayout
-        android:layout_width="0dip"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:paddingRight="@dimen/reading_list_row_padding_right"
-        android:orientation="vertical"
-        android:gravity="center_vertical">
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingBottom="4dp"
-            style="@style/Widget.ReadingListRow.Title" />
-
-        <TextView
-            android:id="@+id/excerpt"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/Widget.ReadingListRow.Description" />
-
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/read_time"
-        android:layout_width="64dp"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:visibility="gone"
-        style="@style/Widget.ReadingListRow.ReadTime" />
-
-</merge>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4762,26 +4762,32 @@ pref("dom.inter-app-communication-api.en
 // Disable mapped array buffer by default.
 pref("dom.mapped_arraybuffer.enabled", false);
 
 // The tables used for Safebrowsing phishing and malware checks.
 pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,test-malware-simple,test-unwanted-simple");
 pref("urlclassifier.phishTable", "goog-phish-shavar,test-phish-simple");
 pref("urlclassifier.downloadBlockTable", "");
 pref("urlclassifier.downloadAllowTable", "");
-pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256");
 
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "test-track-simple,mozstd-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
 pref("browser.safebrowsing.provider.mozilla.lists", "mozstd-track-digest256,mozstd-trackwhite-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+// Block lists for tracking protection. The name values will be used as the keys
+// to lookup the localized name in preferences.properties.
+pref("browser.safebrowsing.provider.mozilla.lists.mozstd.name", "mozstdName");
+pref("browser.safebrowsing.provider.mozilla.lists.mozstd.description", "mozstdDesc");
+pref("browser.safebrowsing.provider.mozilla.lists.mozfull.name", "mozfullName");
+pref("browser.safebrowsing.provider.mozilla.lists.mozfull.description", "mozfullDesc");
 
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
 // Original caret implementation on collapsed selection.
 pref("touchcaret.enabled", false);
 
 // This will inflate the size of the touch caret frame when checking if user
--- a/toolkit/components/telemetry/TelemetryReportingPolicy.jsm
+++ b/toolkit/components/telemetry/TelemetryReportingPolicy.jsm
@@ -179,26 +179,27 @@ var TelemetryReportingPolicyImpl = {
     return this._logger;
   },
 
   /**
    * Get the date the policy was notified.
    * @return {Object} A date object or null on errors.
    */
   get dataSubmissionPolicyNotifiedDate() {
-    if (!Preferences.has(PREF_ACCEPTED_POLICY_DATE)) {
+    let prefString = Preferences.get(PREF_ACCEPTED_POLICY_DATE, "0");
+    let valueInteger = parseInt(prefString, 10);
+
+    // Bail out if we didn't store any value yet.
+    if (valueInteger == 0) {
       this._log.info("get dataSubmissionPolicyNotifiedDate - No date stored yet.");
       return null;
     }
 
-    let prefString = Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0);
-    let valueInteger = parseInt(prefString, 10);
-
-    // If nothing or an invalid value is saved in the prefs, bail out.
-    if (Number.isNaN(valueInteger) || valueInteger == 0) {
+    // If an invalid value is saved in the prefs, bail out too.
+    if (Number.isNaN(valueInteger)) {
       this._log.error("get dataSubmissionPolicyNotifiedDate - Invalid date stored.");
       return null;
     }
 
     // Make sure the notification date is newer then the oldest allowed date.
     let date = new Date(valueInteger);
     if (date.getFullYear() < OLDEST_ALLOWED_ACCEPTANCE_YEAR) {
       this._log.error("get dataSubmissionPolicyNotifiedDate - The stored date is too old.");
--- a/toolkit/devtools/server/actors/animation.js
+++ b/toolkit/devtools/server/actors/animation.js
@@ -23,28 +23,23 @@
  *   http://w3c.github.io/web-animations/
  * - WebAnimation WebIDL files:
  *   /dom/webidl/Animation*.webidl
  */
 
 const {Cu} = require("chrome");
 const promise = require("promise");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
-const {setInterval, clearInterval} = require("sdk/timers");
 const protocol = require("devtools/server/protocol");
 const {ActorClass, Actor, FrontClass, Front,
        Arg, method, RetVal, types} = protocol;
 // Make sure the nodeActor type is know here.
 const {NodeActor} = require("devtools/server/actors/inspector");
 const events = require("sdk/event/core");
 
-// How long (in ms) should we wait before polling again the state of an
-// animationPlayer.
-const PLAYER_DEFAULT_AUTO_REFRESH_TIMEOUT = 500;
-
 // Types of animations.
 const ANIMATION_TYPES = {
   CSS_ANIMATION: "cssanimation",
   CSS_TRANSITION: "csstransition",
   UNKNOWN: "unknown"
 };
 
 /**
@@ -262,23 +257,33 @@ var AnimationPlayerActor = ActorClass({
 
   /**
    * Get the current state of the AnimationPlayer (currentTime, playState, ...).
    * Note that the initial state is returned as the form of this actor when it
    * is initialized.
    * @return {Object}
    */
   getCurrentState: method(function() {
+    // Remember the startTime each time getCurrentState is called, it may be
+    // useful when animations get paused. As in, when an animation gets paused,
+    // it's startTime goes back to null, but the front-end might still be
+    // interested in knowing what the previous startTime was. So everytime it
+    // is set, remember it and send it along with the newState.
+    if (this.player.startTime) {
+      this.previousStartTime = this.player.startTime;
+    }
+
     // Note that if you add a new property to the state object, make sure you
     // add the corresponding property in the AnimationPlayerFront' initialState
     // getter.
     let newState = {
       type: this.getType(),
       // startTime is null whenever the animation is paused or waiting to start.
       startTime: this.player.startTime,
+      previousStartTime: this.previousStartTime,
       currentTime: this.player.currentTime,
       playState: this.player.playState,
       playbackRate: this.player.playbackRate,
       name: this.getName(),
       duration: this.getDuration(),
       delay: this.getDelay(),
       iterationCount: this.getIterationCount(),
       // isRunningOnCompositor is important for developers to know if their
@@ -408,46 +413,44 @@ var AnimationPlayerActor = ActorClass({
     request: {
       currentTime: Arg(0, "number")
     },
     response: {}
   })
 });
 
 var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
-  AUTO_REFRESH_EVENT: "updated-state",
-
   initialize: function(conn, form, detail, ctx) {
     Front.prototype.initialize.call(this, conn, form, detail, ctx);
 
     this.state = {};
   },
 
   form: function(form, detail) {
     if (detail === "actorid") {
       this.actorID = form;
       return;
     }
     this._form = form;
     this.state = this.initialState;
   },
 
   destroy: function() {
-    this.stopAutoRefresh();
     Front.prototype.destroy.call(this);
   },
 
   /**
    * Getter for the initial state of the player. Up to date states can be
    * retrieved by calling the getCurrentState method.
    */
   get initialState() {
     return {
       type: this._form.type,
       startTime: this._form.startTime,
+      previousStartTime: this._form.previousStartTime,
       currentTime: this._form.currentTime,
       playState: this._form.playState,
       playbackRate: this._form.playbackRate,
       name: this._form.name,
       duration: this._form.duration,
       delay: this._form.delay,
       iterationCount: this._form.iterationCount,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
@@ -459,82 +462,24 @@ var AnimationPlayerFront = FrontClass(An
    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
    * update the local knowledge of the state.
    */
   onChanged: protocol.preEvent("changed", function(partialState) {
     let {state} = this.reconstructState(partialState);
     this.state = state;
   }),
 
-  // About auto-refresh:
-  //
-  // The AnimationPlayerFront is capable of automatically refreshing its state
-  // by calling the getCurrentState method at regular intervals. This allows
-  // consumers to update their knowledge of the player's currentTime, playState,
-  // ... dynamically.
-  //
-  // Calling startAutoRefresh will start the automatic refreshing of the state,
-  // and calling stopAutoRefresh will stop it.
-  // Once the automatic refresh has been started, the AnimationPlayerFront emits
-  // "updated-state" events everytime the state changes.
-  //
-  // Note that given the time-related nature of animations, the actual state
-  // changes a lot more often than "updated-state" events are emitted. This is
-  // to avoid making many protocol requests.
-
   /**
-   * Start auto-refreshing this player's state.
-   * @param {Number} interval Optional auto-refresh timer interval to override
-   * the default value.
-   */
-  startAutoRefresh: function(interval=PLAYER_DEFAULT_AUTO_REFRESH_TIMEOUT) {
-    if (this.autoRefreshTimer) {
-      return;
-    }
-
-    this.autoRefreshTimer = setInterval(() => {
-      // Save the refresh promise for tests. The tests need to detect when the
-      // last request completes or they might finish too early.
-      // Storing the latest Promise is enough to know that there is no pending
-      // requests left as p.js guarantees the last request will get the reply
-      // last.
-      this.pendingRefreshStatePromise = this.refreshState();
-      this.pendingRefreshStatePromise.then(() => {
-        this.pendingRefreshStatePromise = null;
-      });
-    }, interval);
-  },
-
-  /**
-   * Stop auto-refreshing this player's state.
-   */
-  stopAutoRefresh: function() {
-    if (!this.autoRefreshTimer) {
-      return;
-    }
-
-    clearInterval(this.autoRefreshTimer);
-    this.autoRefreshTimer = null;
-  },
-
-  /**
-   * Called automatically when auto-refresh is on. Doesn't return anything, but
-   * emits the "updated-state" event.
+   * Refresh the current state of this animation on the client from information
+   * found on the server. Doesn't return anything, just stores the new state.
    */
   refreshState: Task.async(function*() {
     let data = yield this.getCurrentState();
-
-    // By the time the new state is received, auto-refresh might be stopped.
-    if (!this.autoRefreshTimer) {
-      return;
-    }
-
     if (this.currentStateHasChanged) {
       this.state = data;
-      events.emit(this, this.AUTO_REFRESH_EVENT, this.state);
     }
   }),
 
   /**
    * getCurrentState interceptor re-constructs incomplete states since the actor
    * only sends the values that have changed.
    */
   getCurrentState: protocol.custom(function() {
@@ -836,16 +781,34 @@ var AnimationsActor = exports.Animations
     }
     return this.pauseAll();
   }, {
     request: {},
     response: {}
   }),
 
   /**
+   * Toggle (play/pause) several animations at the same time.
+   * @param {Array} players A list of AnimationPlayerActor objects.
+   * @param {Boolean} shouldPause If set to true, the players will be paused,
+   * otherwise they will be played.
+   */
+  toggleSeveral: method(function(players, shouldPause) {
+    return promise.all(players.map(player => {
+      return shouldPause ? player.pause() : player.play();
+    }));
+  }, {
+    request: {
+      players: Arg(0, "array:animationplayer"),
+      shouldPause: Arg(1, "boolean")
+    },
+    response: {}
+  }),
+
+  /**
    * Set the current time of several animations at the same time.
    * @param {Array} players A list of AnimationPlayerActor.
    * @param {Number} time The new currentTime.
    * @param {Boolean} shouldPause Should the players be paused too.
    */
   setCurrentTimes: method(function(players, time, shouldPause) {
     return promise.all(players.map(player => {
       let pause = shouldPause ? player.pause() : promise.resolve();
--- a/toolkit/devtools/server/tests/browser/browser.ini
+++ b/toolkit/devtools/server/tests/browser/browser.ini
@@ -21,18 +21,16 @@ support-files =
   timeline-iframe-parent.html
   director-script-target.html
 
 [browser_animation_actors_01.js]
 [browser_animation_actors_02.js]
 [browser_animation_actors_03.js]
 [browser_animation_actors_04.js]
 skip-if = e10s # Bug 1183605 - toolkit/devtools/server/tests/browser/ tests are still disabled in E10S
-[browser_animation_actors_05.js]
-skip-if = e10s # Bug 1183605 - toolkit/devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_animation_actors_06.js]
 [browser_animation_actors_07.js]
 [browser_animation_actors_08.js]
 [browser_animation_actors_09.js]
 [browser_animation_actors_10.js]
 [browser_animation_actors_11.js]
 [browser_animation_actors_12.js]
 [browser_animation_actors_13.js]
deleted file mode 100644
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_05.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Check that AnimationPlayers can auto-refresh their states.
-
-const {AnimationsFront} = require("devtools/server/actors/animation");
-const {InspectorFront} = require("devtools/server/actors/inspector");
-
-add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
-
-  initDebuggerServer();
-  let client = new DebuggerClient(DebuggerServer.connectPipe());
-  let form = yield connectDebuggerClient(client);
-  let inspector = InspectorFront(client, form);
-  let walker = yield inspector.getWalker();
-  let front = AnimationsFront(client, form);
-
-  let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
-  let [player] = yield front.getAnimationPlayersForNode(node);
-
-  ok(player.startAutoRefresh, "The startAutoRefresh function is available");
-  ok(player.stopAutoRefresh, "The stopAutoRefresh function is available");
-  ok(player.state, "The current state is stored on the player");
-
-  info("Subscribe to the refresh event, start the auto-refresh and wait for " +
-    "a few events to be received");
-
-  player.startAutoRefresh();
-
-  let onAllEventsReceived = new Promise(resolve => {
-    let expected = 5;
-    let previousState = player.initialState;
-    let onNewState = state => {
-      ok(state.currentTime !== previousState.currentTime,
-        "The time has changed since the last update");
-      expected --;
-      previousState = state;
-      if (expected === 0) {
-        player.off(player.AUTO_REFRESH_EVENT, onNewState);
-
-        info("Stop the auto-refresh");
-        player.stopAutoRefresh();
-
-        if (player.pendingRefreshStatePromise) {
-          // A new request was fired before we had chance to stop it. Wait for
-          // it to complete.
-          player.pendingRefreshStatePromise.then(resolve);
-        } else {
-          resolve();
-        }
-      }
-    };
-    player.on(player.AUTO_REFRESH_EVENT, onNewState);
-  });
-
-  yield onAllEventsReceived;
-
-  yield closeDebuggerClient(client);
-  gBrowser.removeCurrentTab();
-});
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_07.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_07.js
@@ -6,17 +6,17 @@
 
 // Check that, even though the AnimationPlayerActor only sends the bits of its
 // state that change, the front reconstructs the whole state everytime.
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+  yield addTab(MAIN_DOMAIN + "animation.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let front = AnimationsFront(client, form);
 
@@ -32,15 +32,16 @@ function* playerHasCompleteStateAtAllTim
   yield player.ready();
 
   // Get the list of state key names from the initialstate.
   let keys = Object.keys(player.initialState);
 
   // Get the state over and over again and check that the object returned
   // contains all keys.
   // Normally, only the currentTime will have changed in between 2 calls.
-  for (let i = 0; i < 10; i ++) {
+  for (let i = 0; i < 10; i++) {
     let state = yield player.getCurrentState();
     keys.forEach(key => {
-      ok(typeof state[key] !== "undefined", "The state retrieved has key " + key);
+      ok(typeof state[key] !== "undefined",
+         "The state retrieved has key " + key);
     });
   }
 }
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_08.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_08.js
@@ -1,55 +1,88 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Check that the AnimationsActor can pause/play all animations at once.
+// Check that the AnimationsActor can pause/play all animations at once, and
+// check that it can also pause/play a given list of animations at once.
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
+// List of selectors that match "all" animated nodes in the test page.
+// This list misses a bunch of animated nodes on purpose. Only the ones that
+// have infinite animations are listed. This is done to avoid intermittents
+// caused when finite animations are already done playing by the time the test
+// runs.
+const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations",
+                            ".delayed-animation"];
+// List of selectors that match some animated nodes in the test page only.
+const SOME_ANIMATED_NODES = [".simple-animation", ".delayed-animation"];
+
 add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+  yield addTab(MAIN_DOMAIN + "animation.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let front = AnimationsFront(client, form);
 
   info("Pause all animations in the test document");
   yield front.pauseAll();
-  yield checkAllAnimationsStates(walker, front, "paused");
+  yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "paused");
 
   info("Play all animations in the test document");
   yield front.playAll();
-  yield checkAllAnimationsStates(walker, front, "running");
+  yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "running");
 
   info("Pause all animations in the test document using toggleAll");
   yield front.toggleAll();
-  yield checkAllAnimationsStates(walker, front, "paused");
+  yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "paused");
 
   info("Play all animations in the test document using toggleAll");
   yield front.toggleAll();
-  yield checkAllAnimationsStates(walker, front, "running");
+  yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "running");
+
+  info("Pause a given list of animations only");
+  let players = [];
+  for (let selector of SOME_ANIMATED_NODES) {
+    let [player] = yield getPlayersFor(walker, front, selector);
+    players.push(player);
+  }
+  yield front.toggleSeveral(players, true);
+  yield checkAnimationsStates(walker, front, SOME_ANIMATED_NODES, "paused");
+  yield checkAnimationsStates(walker, front, [".multiple-animations"], "running");
+
+  info("Play the same list of animations");
+  yield front.toggleSeveral(players, false);
+  yield checkAnimationsStates(walker, front, ALL_ANIMATED_NODES, "running");
 
   yield closeDebuggerClient(client);
   gBrowser.removeCurrentTab();
 });
 
-function* checkAllAnimationsStates(walker, front, playState) {
-  info("Checking the playState of all the nodes that have infinite running animations");
+function* checkAnimationsStates(walker, front, selectors, playState) {
+  info("Checking the playState of all the nodes that have infinite running " +
+       "animations");
 
-  let selectors = [".simple-animation", ".multiple-animations", ".delayed-animation"];
   for (let selector of selectors) {
     info("Getting the AnimationPlayerFront for node " + selector);
-    let node = yield walker.querySelector(walker.rootNode, selector);
-    let [player] = yield front.getAnimationPlayersForNode(node);
-    yield player.ready;
-    let state = yield player.getCurrentState();
-    is(state.playState, playState,
-      "The playState of node " + selector + " is " + playState);
+    let [player] = yield getPlayersFor(walker, front, selector);
+    yield player.ready();
+    yield checkPlayState(player, selector, playState);
   }
 }
+
+function* getPlayersFor(walker, front, selector) {
+  let node = yield walker.querySelector(walker.rootNode, selector);
+  return front.getAnimationPlayersForNode(node);
+}
+
+function* checkPlayState(player, selector, expectedState) {
+  let state = yield player.getCurrentState();
+  is(state.playState, expectedState,
+    "The playState of node " + selector + " is " + expectedState);
+}
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_09.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_09.js
@@ -9,17 +9,17 @@
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 const URL = MAIN_DOMAIN + "animation.html";
 
 add_task(function*() {
   info("Creating a test document with 2 iframes containing animated nodes");
-  let doc = yield addTab("data:text/html;charset=utf-8," +
+  yield addTab("data:text/html;charset=utf-8," +
                          "<iframe id='i1' src='" + URL + "'></iframe>" +
                          "<iframe id='i2' src='" + URL + "'></iframe>");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
@@ -43,16 +43,17 @@ add_task(function*() {
   gBrowser.removeCurrentTab();
 });
 
 function* checkState(front, nodeFront, playState) {
   info("Getting the AnimationPlayerFront for the test node");
   let [player] = yield front.getAnimationPlayersForNode(nodeFront);
   yield player.ready;
   let state = yield player.getCurrentState();
-  is(state.playState, playState, "The playState of the test node is " + playState);
+  is(state.playState, playState,
+     "The playState of the test node is " + playState);
 }
 
 function* getNodeInFrame(walker, frameSelector, nodeSelector) {
   let iframe = yield walker.querySelector(walker.rootNode, frameSelector);
   let {nodes} = yield walker.children(iframe);
   return walker.querySelector(nodes[0], nodeSelector);
 }
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_10.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_10.js
@@ -9,50 +9,56 @@
 // the player objects.
 // See toolkit/devtools/server/actors/animation.js |getPlayerIndex| for more
 // information.
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+  yield addTab(MAIN_DOMAIN + "animation.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let front = AnimationsFront(client, form);
 
   info("Retrieve a non animated node");
   let node = yield walker.querySelector(walker.rootNode, ".not-animated");
 
   info("Apply the multiple-animations-2 class to start the animations");
   yield node.modifyAttributes([
     {attributeName: "class", newValue: "multiple-animations-2"}
   ]);
 
-  info("Get the list of players, by the time this executes, the first, short, " +
-    "animation should have ended.");
+  info("Get the list of players, by the time this executes, the first, " +
+       "short, animation should have ended.");
   let players = yield front.getAnimationPlayersForNode(node);
   if (players.length === 3) {
     info("The short animation hasn't ended yet, wait for a bit.");
     // The animation lasts for 500ms, so 1000ms should do it.
     yield new Promise(resolve => setTimeout(resolve, 1000));
 
     info("And get the list again");
     players = yield front.getAnimationPlayersForNode(node);
   }
 
   is(players.length, 2, "2 animations remain on the node");
 
-  is(players[0].state.duration, 1000, "The duration of the first animation is correct");
-  is(players[0].state.delay, 2000, "The delay of the first animation is correct");
-  is(players[0].state.iterationCount, null, "The iterationCount of the first animation is correct");
+  is(players[0].state.duration, 1000,
+     "The duration of the first animation is correct");
+  is(players[0].state.delay, 2000,
+     "The delay of the first animation is correct");
+  is(players[0].state.iterationCount, null,
+     "The iterationCount of the first animation is correct");
 
-  is(players[1].state.duration, 3000, "The duration of the second animation is correct");
-  is(players[1].state.delay, 1000, "The delay of the second animation is correct");
-  is(players[1].state.iterationCount, 100, "The iterationCount of the second animation is correct");
+  is(players[1].state.duration, 3000,
+     "The duration of the second animation is correct");
+  is(players[1].state.delay, 1000,
+     "The delay of the second animation is correct");
+  is(players[1].state.iterationCount, 100,
+     "The iterationCount of the second animation is correct");
 
   yield closeDebuggerClient(client);
   gBrowser.removeCurrentTab();
 });
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_12.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_12.js
@@ -5,17 +5,17 @@
 "use strict";
 
 // Check that a player's playbackRate can be changed.
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+  yield addTab(MAIN_DOMAIN + "animation.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let animations = AnimationsFront(client, form);
 
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_13.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_13.js
@@ -6,17 +6,17 @@
 
 // Test that the AnimationsActor emits events about changed animations on a
 // node after getAnimationPlayersForNode was called on that node.
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+  yield addTab(MAIN_DOMAIN + "animation.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let animations = AnimationsFront(client, form);
 
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_14.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_14.js
@@ -8,17 +8,17 @@
 // Indeed, animations that only have the "finished" playState can be modified
 // still, so we want the AnimationsActor to preserve the corresponding
 // AnimationPlayerActor.
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 add_task(function*() {
-  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+  yield addTab(MAIN_DOMAIN + "animation.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let animations = AnimationsFront(client, form);
 
--- a/toolkit/devtools/server/tests/browser/browser_animation_actors_16.js
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_16.js
@@ -9,18 +9,18 @@
 
 const {AnimationsFront} = require("devtools/server/actors/animation");
 const {InspectorFront} = require("devtools/server/actors/inspector");
 
 const URL = MAIN_DOMAIN + "animation.html";
 
 add_task(function*() {
   info("Creating a test document with 2 iframes containing animated nodes");
-  let doc = yield addTab("data:text/html;charset=utf-8," +
-                         "<iframe id='iframe' src='" + URL + "'></iframe>");
+  yield addTab("data:text/html;charset=utf-8," +
+               "<iframe id='iframe' src='" + URL + "'></iframe>");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let inspector = InspectorFront(client, form);
   let walker = yield inspector.getWalker();
   let front = AnimationsFront(client, form);