Merge m-c to b2ginbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 18 Sep 2015 14:05:20 -0700
changeset 295972 070ec91af2cd2da86d11ca2fd7a64ff3a0c7498f
parent 295971 fcd3f150cc0c7311b69340bebcd9a50d42263ad6 (current diff)
parent 295879 e44ee5f2679134c2ff210f93e42c5a86865ade52 (diff)
child 295973 6c239902857fb11b77cc57d958d6d1ebccebd671
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 m-c to b2ginbound, a=merge
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
intl/hyphenation/COPYING
intl/hyphenation/COPYING.LGPL
intl/hyphenation/COPYING.MPL
intl/hyphenation/README
intl/hyphenation/README.compound
intl/hyphenation/README.hyphen
intl/hyphenation/README.nonstandard
intl/hyphenation/hnjalloc.h
intl/hyphenation/hnjstdio.cpp
intl/hyphenation/hyphen.c
intl/hyphenation/hyphen.h
intl/hyphenation/moz.build
intl/hyphenation/nsHyphenationManager.cpp
intl/hyphenation/nsHyphenationManager.h
intl/hyphenation/nsHyphenator.cpp
intl/hyphenation/nsHyphenator.h
mobile/android/base/resources/layout/reading_list_row_view.xml
testing/web-platform/meta/content-security-policy/blink-contrib-2/scripthash-allowed.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib-2/scriptnonce-allowed.sub.html.ini
testing/web-platform/meta/content-security-policy/blink-contrib-2/scriptnonce-redirect.sub.html.ini
toolkit/devtools/server/tests/browser/browser_animation_actors_05.js
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -111,26 +111,34 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccess
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
   tmp->mDependentIDsHash.EnumerateRead(CycleCollectorTraverseDepIDsEntry, &cb);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
+  for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) {
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mOwner)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mChild)
+  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
   tmp->mDependentIDsHash.Clear();
   tmp->mNodeToAccessibleMap.Clear();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
+  for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) {
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mARIAOwnsInvalidationList[i].mOwner)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mARIAOwnsInvalidationList[i].mChild)
+  }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
@@ -1335,16 +1343,20 @@ DocAccessible::ProcessInvalidationList()
     }
   }
 
   mInvalidationList.Clear();
 
   // Alter the tree according to aria-owns (seize the trees).
   for (uint32_t idx = 0; idx < mARIAOwnsInvalidationList.Length(); idx++) {
     Accessible* owner = mARIAOwnsInvalidationList[idx].mOwner;
+    if (owner->IsDefunct()) { // eventually died until we've got here
+      continue;
+    }
+
     Accessible* child = GetAccessible(mARIAOwnsInvalidationList[idx].mChild);
     if (!child) {
       continue;
     }
 
     // XXX: update context flags
     {
       Accessible* oldParent = child->Parent();
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -676,18 +676,18 @@ protected:
   struct ARIAOwnsPair {
     ARIAOwnsPair(Accessible* aOwner, nsIContent* aChild) :
       mOwner(aOwner), mChild(aChild) { }
     ARIAOwnsPair(const ARIAOwnsPair& aPair) :
       mOwner(aPair.mOwner), mChild(aPair.mChild) { }
     ARIAOwnsPair& operator =(const ARIAOwnsPair& aPair)
       { mOwner = aPair.mOwner; mChild = aPair.mChild; return *this; }
 
-    Accessible* mOwner;
-    nsIContent* mChild;
+    nsRefPtr<Accessible> mOwner;
+    nsCOMPtr<nsIContent> mChild;
   };
   nsTArray<ARIAOwnsPair> mARIAOwnsInvalidationList;
 
   /**
    * Used to process notification from core and accessible events.
    */
   nsRefPtr<NotificationController> mNotificationController;
   friend class EventQueue;
--- 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
@@ -1832,16 +1830,20 @@ pref("identity.fxaccounts.migrateToDevEd
 #ifdef MOZ_WIDGET_GTK
 pref("ui.key.menuAccessKeyFocuses", true);
 #endif
 
 // Encrypted media extensions.
 pref("media.eme.enabled", true);
 pref("media.eme.apiVisible", true);
 
+// Whether we should run a test-pattern through EME GMPs before assuming they'll
+// decode H.264.
+pref("media.gmp.trial-create.enabled", true);
+
 #ifdef MOZ_ADOBE_EME
 pref("browser.eme.ui.enabled", true);
 pref("media.gmp-eme-adobe.enabled", true);
 #endif
 
 // Play with different values of the decay time and get telemetry,
 // 0 means to randomize (and persist) the experiment value in users' profiles,
 // -1 means no experiment is run and we use the preferred value for frecency (6h)
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -274,16 +274,17 @@ skip-if = buildapp == "mulet" || e10s # 
 tags = mcb
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win' || e10s # Bug 1159268 - Need a content-process safe version of synthesizeWheel
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_bug1070778.js]
+[browser_accesskeys.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
 [browser_clipboard.js]
 [browser_contentAreaClick.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" || e10s # bug 967013; e10s: bug 1094761 - test hits the network in e10s, causing next test to crash
 [browser_ctrlTab.js]
 [browser_datareporting_notification.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_accesskeys.js
@@ -0,0 +1,82 @@
+add_task(function *() {
+  yield pushPrefs(["ui.key.contentAccess", 5], ["ui.key.chromeAccess", 5]);
+
+  const gPageURL1 = "data:text/html,<body><p>" +
+                    "<button id='button' accesskey='y'>Button</button>" +
+                    "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" +
+                    "</p></body>";
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1);
+  tab1.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+  Services.focus.clearFocus(window);
+
+  // Press an accesskey in the child document while the chrome is focused.
+  let focusedId = yield performAccessKey("y");
+  is(focusedId, "button", "button accesskey");
+
+  // Press an accesskey in the child document while the content document is focused.
+  focusedId = yield performAccessKey("z");
+  is(focusedId, "checkbox", "checkbox accesskey");
+
+  // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused.
+  let newButton = document.createElement("button");
+  newButton.id = "chromebutton";
+  newButton.setAttribute("accesskey", "z");
+  document.documentElement.appendChild(newButton);
+
+  Services.focus.clearFocus(window);
+
+  focusedId = yield performAccessKeyForChrome("z");
+  is(focusedId, "chromebutton", "chromebutton accesskey");
+
+  // Add a second tab and ensure that accesskey from the first tab is not used.
+  const gPageURL2 = "data:text/html,<body>" +
+                    "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" +
+                    "</body>";
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2);
+  tab2.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+  Services.focus.clearFocus(window);
+
+  focusedId = yield performAccessKey("y");
+  is(focusedId, "tab2button", "button accesskey in tab2");
+
+  // Press the accesskey for the chrome element while the content document is focused.
+  focusedId = yield performAccessKeyForChrome("z");
+  is(focusedId, "chromebutton", "chromebutton accesskey");
+
+  newButton.parentNode.removeChild(newButton);
+
+  gBrowser.removeTab(tab1);
+  gBrowser.removeTab(tab2);
+});
+
+function childHandleFocus() {
+  content.document.body.firstChild.addEventListener("focus", function focused(event) {
+    let focusedElement = content.document.activeElement;
+    focusedElement.blur();
+    sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id })
+  }, true);
+}
+
+function performAccessKey(key)
+{
+  return new Promise(resolve => {
+    let mm = gBrowser.selectedBrowser.messageManager;
+    mm.addMessageListener("Test:FocusFromAccessKey", function listenForFocus(msg) {
+      mm.removeMessageListener("Test:FocusFromAccessKey", listenForFocus);
+      resolve(msg.data.focus);
+    });
+
+    EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
+  });
+}
+
+// This version is used when a chrome elemnt is expected to be found for an accesskey.
+function* performAccessKeyForChrome(key, inChild)
+{
+  let waitFocusChangePromise = BrowserTestUtils.waitForEvent(document, "focus", true);
+  EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
+  yield waitFocusChangePromise;
+  return document.activeElement.id;
+}
--- 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/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -128,32 +128,37 @@ function getPrefReaderForType(t) {
  * Converts a number of bytes to the appropriate unit that results in a
  * number that needs fewer than 4 digits
  *
  * @return a pair: [new value with 3 sig. figs., its unit]
   */
 function convertByteUnits(aBytes) {
   var units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
   let unitIndex = 0;
- 
+
   // convert to next unit if it needs 4 digits (after rounding), but only if
   // we know the name of the next unit
   while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
     aBytes /= 1024;
     unitIndex++;
   }
- 
+
   // Get rid of insignificant bits by truncating to 1 or 0 decimal points
   // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
   aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
- 
+
   return [aBytes, units[unitIndex]];
 }
 
-function FeedWriter() {}
+function FeedWriter() {
+  this._selectedApp = undefined;
+  this._selectedAppMenuItem = null;
+  this._defaultHandlerMenuItem = null;
+}
+
 FeedWriter.prototype = {
   _mimeSvc      : Cc["@mozilla.org/mime;1"].
                   getService(Ci.nsIMIMEService),
 
   _getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
     return container.fields.getProperty(property).
                      QueryInterface(Ci.nsIPropertyBag2);
   },
@@ -163,99 +168,54 @@ FeedWriter.prototype = {
       return container.fields.getPropertyAsAString(property);
     }
     catch (e) {
     }
     return "";
   },
 
   _setContentText: function FW__setContentText(id, text) {
-    this._contentSandbox.element = this._document.getElementById(id);
-    this._contentSandbox.textNode = text.createDocumentFragment(this._contentSandbox.element);
-    var codeStr =
-      "while (element.hasChildNodes()) " +
-      "  element.removeChild(element.firstChild);" +
-      "element.appendChild(textNode);";
+    var element = this._document.getElementById(id);
+    var textNode = text.createDocumentFragment(element);
+    while (element.hasChildNodes())
+      element.removeChild(element.firstChild);
+    element.appendChild(textNode);
     if (text.base) {
-      this._contentSandbox.spec = text.base.spec;
-      codeStr += "element.setAttributeNS('" + XML_NS + "', 'base', spec);";
+      element.setAttributeNS(XML_NS, 'base', text.base.spec);
     }
-    Cu.evalInSandbox(codeStr, this._contentSandbox);
-    this._contentSandbox.element = null;
-    this._contentSandbox.textNode = null;
   },
 
   /**
    * Safely sets the href attribute on an anchor tag, providing the URI 
    * specified can be loaded according to rules. 
    * @param   element
    *          The element to set a URI attribute on
    * @param   attribute
    *          The attribute of the element to set the URI to, e.g. href or src
    * @param   uri
    *          The URI spec to set as the href
    */
-  _safeSetURIAttribute: 
+  _safeSetURIAttribute:
   function FW__safeSetURIAttribute(element, attribute, uri) {
     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
-                 getService(Ci.nsIScriptSecurityManager);    
+                 getService(Ci.nsIScriptSecurityManager);
     const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
     try {
+      // TODO Is this necessary?
       secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
       // checkLoadURIStrWithPrincipal will throw if the link URI should not be
       // loaded, either because our feedURI isn't allowed to load it or per
       // the rules specified in |flags|, so we'll never "linkify" the link...
     }
     catch (e) {
       // Not allowed to load this link because secman.checkLoadURIStr threw
       return;
     }
 
-    this._contentSandbox.element = element;
-    this._contentSandbox.uri = uri;
-    var codeStr = "element.setAttribute('" + attribute + "', uri);";
-    Cu.evalInSandbox(codeStr, this._contentSandbox);
-  },
-
-  /**
-   * Use this sandbox to run any dom manipulation code on nodes which
-   * are already inserted into the content document.
-   */
-  __contentSandbox: null,
-  get _contentSandbox() {
-    // This whole sandbox setup is totally archaic. It was introduced in bug
-    // 360529, presumably before the existence of a solid security membrane,
-    // since all of the manipulation of content here should be made safe by
-    // Xrays. And now that anonymous content is no longer content-accessible,
-    // manipulating the xml stylesheet content can't be done from content
-    // anymore.
-    //
-    // The right solution would be to rip out all of this sandbox junk and
-    // manipulate the DOM directly. But that's a big yak to shave, so for now,
-    // we just give the sandbox an nsExpandedPrincipal with []. This has the
-    // effect of giving it Xrays, and making it same-origin with the XBL scope,
-    // thereby letting it manipulate anonymous content.
-    if (!this.__contentSandbox)
-      this.__contentSandbox = new Cu.Sandbox([this._window],
-                                             {sandboxName: 'FeedWriter'});
-
-    return this.__contentSandbox;
-  },
-
-  /**
-   * Calls doCommand for a given XUL element within the context of the
-   * content document.
-   *
-   * @param aElement
-   *        the XUL element to call doCommand() on.
-   */
-  _safeDoCommand: function FW___safeDoCommand(aElement) {
-    this._contentSandbox.element = aElement;
-    Cu.evalInSandbox("element.doCommand();", this._contentSandbox);
-    this._contentSandbox.element = null;
+    element.setAttribute(attribute, uri);
   },
 
   __faviconService: null,
   get _faviconService() {
     if (!this.__faviconService)
       this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
                               getService(Ci.nsIFaviconService);
 
@@ -289,44 +249,39 @@ FeedWriter.prototype = {
 
       node = node.nextSibling;
     }
 
     return null;
   },
 
   _setCheckboxCheckedState: function FW__setCheckboxCheckedState(aCheckbox, aValue) {
-    // see checkbox.xml, xbl bindings are not applied within the sandbox!
-    this._contentSandbox.checkbox = aCheckbox;
-    var codeStr;
+    // see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
     var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
     if (aValue)
-      codeStr = "checkbox.setAttribute('checked', 'true'); ";
+      aCheckbox.setAttribute('checked', 'true');
     else
-      codeStr = "checkbox.removeAttribute('checked'); ";
+      aCheckbox.removeAttribute('checked');
 
     if (change) {
-      this._contentSandbox.document = this._document;
-      codeStr += "var event = document.createEvent('Events'); " +
-                 "event.initEvent('CheckboxStateChange', true, true);" +
-                 "checkbox.dispatchEvent(event);"
+      var event = this._document.createEvent('Events');
+      event.initEvent('CheckboxStateChange', true, true);
+      aCheckbox.dispatchEvent(event);
     }
-
-    Cu.evalInSandbox(codeStr, this._contentSandbox);
   },
 
    /**
    * Returns a date suitable for displaying in the feed preview. 
    * If the date cannot be parsed, the return value is "false".
    * @param   dateString
    *          A date as extracted from a feed entry. (entry.updated)
    */
   _parseDate: function FW__parseDate(dateString) {
     // Convert the date into the user's local time zone
-    dateObj = new Date(dateString);
+    var dateObj = new Date(dateString);
 
     // Make sure the date we're given is valid.
     if (!dateObj.getTime())
       return false;
 
     var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
                       getService(Ci.nsIScriptableDateFormat);
     return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
@@ -373,62 +328,52 @@ FeedWriter.prototype = {
    * Writes the feed title into the preview document.
    * @param   container
    *          The feed container
    */
   _setTitleText: function FW__setTitleText(container) {
     if (container.title) {
       var title = container.title.plainText();
       this._setContentText(TITLE_ID, container.title);
-      this._contentSandbox.document = this._document;
-      this._contentSandbox.title = title;
-      var codeStr = "document.title = title;"
-      Cu.evalInSandbox(codeStr, this._contentSandbox);
+      this._document.title = title;
     }
 
     var feed = container.QueryInterface(Ci.nsIFeed);
     if (feed && feed.subtitle)
       this._setContentText(SUBTITLE_ID, container.subtitle);
   },
 
   /**
    * Writes the title image into the preview document if one is present.
    * @param   container
    *          The feed container
    */
   _setTitleImage: function FW__setTitleImage(container) {
     try {
       var parts = container.image;
-      
+
       // Set up the title image (supplied by the feed)
       var feedTitleImage = this._document.getElementById("feedTitleImage");
       this._safeSetURIAttribute(feedTitleImage, "src", 
                                 parts.getPropertyAsAString("url"));
 
       // Set up the title image link
       var feedTitleLink = this._document.getElementById("feedTitleLink");
 
       var titleText = this._getFormattedString("linkTitleTextFormat", 
                                                [parts.getPropertyAsAString("title")]);
-      this._contentSandbox.feedTitleLink = feedTitleLink;
-      this._contentSandbox.titleText = titleText;
-      this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
-      this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
+      var feedTitleText = this._document.getElementById("feedTitleText");
+      var titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
 
       // Fix the margin on the main title, so that the image doesn't run over
       // the underline
-      var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
-                    "feedTitleText.style.marginRight = titleImageWidth + 'px';";
-      Cu.evalInSandbox(codeStr, this._contentSandbox);
-      this._contentSandbox.feedTitleLink = null;
-      this._contentSandbox.titleText = null;
-      this._contentSandbox.feedTitleText = null;
-      this._contentSandbox.titleImageWidth = null;
+      feedTitleLink.setAttribute('title', titleText);
+      feedTitleText.style.marginRight = titleImageWidth + 'px';
 
-      this._safeSetURIAttribute(feedTitleLink, "href", 
+      this._safeSetURIAttribute(feedTitleLink, "href",
                                 parts.getPropertyAsAString("link"));
     }
     catch (e) {
       LOG("Failed to set Title Image (this is benign): " + e);
     }
   },
 
   /**
@@ -437,18 +382,17 @@ FeedWriter.prototype = {
    *          The container of entries in the feed
    */
   _writeFeedContent: function FW__writeFeedContent(container) {
     // Build the actual feed content
     var feed = container.QueryInterface(Ci.nsIFeed);
     if (feed.items.length == 0)
       return;
 
-    this._contentSandbox.feedContent =
-      this._document.getElementById("feedContent");
+    var feedContent = this._document.getElementById("feedContent");
 
     for (var i = 0; i < feed.items.length; ++i) {
       var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
       entry.QueryInterface(Ci.nsIFeedContainer);
 
       var entryContainer = this._document.createElementNS(HTML_NS, "div");
       entryContainer.className = "entry";
 
@@ -505,29 +449,22 @@ FeedWriter.prototype = {
       body.className = "feedEntryContent";
       entryContainer.appendChild(body);
 
       if (entry.enclosures && entry.enclosures.length > 0) {
         var enclosuresDiv = this._buildEnclosureDiv(entry);
         entryContainer.appendChild(enclosuresDiv);
       }
 
-      this._contentSandbox.entryContainer = entryContainer;
-      this._contentSandbox.clearDiv =
-        this._document.createElementNS(HTML_NS, "div");
-      this._contentSandbox.clearDiv.style.clear = "both";
-      
-      var codeStr = "feedContent.appendChild(entryContainer); " +
-                     "feedContent.appendChild(clearDiv);"
-      Cu.evalInSandbox(codeStr, this._contentSandbox);
+      var clearDiv = this._document.createElementNS(HTML_NS, "div");
+      clearDiv.style.clear = "both";
+
+      feedContent.appendChild(entryContainer);
+      feedContent.appendChild(clearDiv);
     }
-
-    this._contentSandbox.feedContent = null;
-    this._contentSandbox.entryContainer = null;
-    this._contentSandbox.clearDiv = null;
   },
 
   /**
    * Takes a url to a media item and returns the best name it can come up with.
    * Frequently this is the filename portion (e.g. passing in 
    * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
    * cases, this will return the entire url (e.g. passing in
    * http://example.com/somedirectory/ would return 
@@ -622,34 +559,32 @@ FeedWriter.prototype = {
     }
 
     return enclosuresDiv;
   },
 
   /**
    * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
    * Displays error information if there was one.
-   * @param   result
-   *          The parsed feed result
    * @returns A valid nsIFeedContainer object containing the contents of
    *          the feed.
    */
-  _getContainer: function FW__getContainer(result) {
-    var feedService = 
+  _getContainer: function FW__getContainer() {
+    var feedService =
         Cc["@mozilla.org/browser/feeds/result-service;1"].
         getService(Ci.nsIFeedResultService);
 
     try {
-      var result = 
+      var result =
         feedService.getFeedResult(this._getOriginalURI(this._window));
     }
     catch (e) {
       LOG("Subscribe Preview: feed not available?!");
     }
-    
+
     if (result.bozo) {
       LOG("Subscribe Preview: feed result is bozo?!");
     }
 
     try {
       var container = result.doc;
     }
     catch (e) {
@@ -703,22 +638,20 @@ FeedWriter.prototype = {
    * Helper method to set the selected application and system default
    * reader menuitems details from a file object
    *   @param aMenuItem
    *          The menuitem on which the attributes should be set
    *   @param aFile
    *          The menuitem's associated file
    */
   _initMenuItemWithFile: function(aMenuItem, aFile) {
-    this._contentSandbox.menuitem = aMenuItem;
-    this._contentSandbox.label = this._getFileDisplayName(aFile);
-    this._contentSandbox.image = this._getFileIconURL(aFile);
-    var codeStr = "menuitem.setAttribute('label', label); " +
-                  "menuitem.setAttribute('image', image);"
-    Cu.evalInSandbox(codeStr, this._contentSandbox);
+    var label = this._getFileDisplayName(aFile);
+    var image = this._getFileIconURL(aFile);
+    aMenuitem.setAttribute('label', label);
+    aMenuitem.setAttribute('image', image);
   },
 
   /**
    * Helper method to get an element in the XBL binding where the handler
    * selection UI lives
    */
   _getUIElement: function FW__getUIElement(id) {
     return this._document.getAnonymousElementByAttribute(
@@ -744,23 +677,22 @@ FeedWriter.prototype = {
 #expand             if (fp.file.leafName != "__MOZ_APP_NAME__.exe") {
 #else
 #ifdef XP_MACOSX
 #expand             if (fp.file.leafName != "__MOZ_MACBUNDLE_NAME__") {
 #else
 #expand             if (fp.file.leafName != "__MOZ_APP_NAME__-bin") {
 #endif
 #endif
-              this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
+              this._initMenuItemWithFile(this._selectedAppMenuItem,
                                          this._selectedApp);
 
               // Show and select the selected application menuitem
-              let codeStr = "selectedAppMenuItem.hidden = false;" +
-                            "selectedAppMenuItem.doCommand();"
-              Cu.evalInSandbox(codeStr, this._contentSandbox);
+              this._selectedAppMenuItem.hidden = false;
+              this._selectedAppMenuItem.doCommand();
               if (aCallback) {
                 aCallback(true);
                 return;
               }
             }
           }
         }
         if (aCallback) {
@@ -798,21 +730,19 @@ FeedWriter.prototype = {
         stringLabel = "subscribeVideoPodcastUsing";
         break;
 
       case Ci.nsIFeed.TYPE_AUDIO:
         stringLabel = "subscribeAudioPodcastUsing";
         break;
     }
 
-    this._contentSandbox.subscribeUsing =
-      this._getUIElement("subscribeUsingDescription");
-    this._contentSandbox.label = this._getString(stringLabel);
-    var codeStr = "subscribeUsing.setAttribute('value', label);"
-    Cu.evalInSandbox(codeStr, this._contentSandbox);
+    var subscribeUsing = this._getUIElement("subscribeUsingDescription");
+    var label = this._getString(stringLabel);
+    subscribeUsing.setAttribute('value', label);
   },
 
   _setAlwaysUseLabel: function FW__setAlwaysUseLabel() {
     var checkbox = this._getUIElement("alwaysUse");
     if (checkbox) {
       if (this._handlersMenuList) {
         var handlerName = this._getSelectedItemFromMenulist(this._handlersMenuList)
                               .getAttribute("label");
@@ -822,21 +752,19 @@ FeedWriter.prototype = {
             stringLabel = "alwaysUseForVideoPodcasts";
             break;
 
           case Ci.nsIFeed.TYPE_AUDIO:
             stringLabel = "alwaysUseForAudioPodcasts";
             break;
         }
 
-        this._contentSandbox.checkbox = checkbox;
-        this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
-        
-        var codeStr = "checkbox.setAttribute('label', label);";
-        Cu.evalInSandbox(codeStr, this._contentSandbox);
+        var label = this._getFormattedString(stringLabel, [handlerName]);
+
+        checkbox.setAttribute('label', label);
       }
     }
   },
 
   // nsIDomEventListener
   handleEvent: function(event) {
     if (event.target.ownerDocument != this._document) {
       LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
@@ -870,17 +798,17 @@ FeedWriter.prototype = {
           break;
         default:
           this._setAlwaysUseLabel();
       }
     }
   },
 
   _setSelectedHandler: function FW__setSelectedHandler(feedType) {
-    var prefs =   
+    var prefs =
         Cc["@mozilla.org/preferences-service;1"].
         getService(Ci.nsIPrefBranch);
 
     var handler = "bookmarks";
     try {
       handler = prefs.getCharPref(getPrefReaderForType(feedType));
     }
     catch (ex) { }
@@ -897,77 +825,74 @@ FeedWriter.prototype = {
           }
           var handlers =
             this._handlersMenuList.getElementsByAttribute("webhandlerurl", url);
           if (handlers.length == 0) {
             LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
             return;
           }
 
-          this._safeDoCommand(handlers[0]);
+          handlers[0].doCommand();
         }
         break;
       }
       case "client": {
         try {
           this._selectedApp =
             prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
         }
         catch(ex) {
           this._selectedApp = null;
         }
 
         if (this._selectedApp) {
-          this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
+          this._initMenuItemWithFile(this._selectedAppMenuItem,
                                      this._selectedApp);
-          var codeStr = "selectedAppMenuItem.hidden = false; " +
-                        "selectedAppMenuItem.doCommand(); ";
+          this._selectedAppMenuItem.hidden = false;
+          this._selectedAppMenuItem.doCommand();
 
           // Only show the default reader menuitem if the default reader
           // isn't the selected application
           if (this._defaultSystemReader) {
             var shouldHide =
               this._defaultSystemReader.path == this._selectedApp.path;
-            codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";"
+            this._defaultHandlerMenuItem.hidden = shouldHide;
           }
-          Cu.evalInSandbox(codeStr, this._contentSandbox);
           break;
         }
       }
       case "bookmarks":
       default: {
         var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
         if (liveBookmarksMenuItem)
-          this._safeDoCommand(liveBookmarksMenuItem);
+          liveBookmarksMenuItem.doCommand();
       } 
     }
   },
 
   _initSubscriptionUI: function FW__initSubscriptionUI() {
     var handlersMenuPopup = this._getUIElement("handlersMenuPopup");
     if (!handlersMenuPopup)
       return;
- 
+
     var feedType = this._getFeedType();
-    var codeStr;
 
     // change the background
     var header = this._document.getElementById("feedHeader");
-    this._contentSandbox.header = header;
     switch (feedType) {
       case Ci.nsIFeed.TYPE_VIDEO:
-        codeStr = "header.className = 'videoPodcastBackground'; ";
+        header.className = 'videoPodcastBackground';
         break;
 
       case Ci.nsIFeed.TYPE_AUDIO:
-        codeStr = "header.className = 'audioPodcastBackground'; ";
+        header.className = 'audioPodcastBackground';
         break;
 
       default:
-        codeStr = "header.className = 'feedBackground'; ";
+        header.className = 'feedBackground';
     }
 
     var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
 
     // Last-selected application
     var menuItem = liveBookmarksMenuItem.cloneNode(false);
     menuItem.removeAttribute("selected");
     menuItem.setAttribute("anonid", "selectedAppMenuItem");
@@ -985,20 +910,19 @@ FeedWriter.prototype = {
         // Hide the menuitem if the last selected application doesn't exist
         menuItem.setAttribute("hidden", true);
       }
     }
     catch(ex) {
       // Hide the menuitem until an application is selected
       menuItem.setAttribute("hidden", true);
     }
-    this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
-    this._contentSandbox.selectedAppMenuItem = menuItem;
-    
-    codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
+    this._selectedAppMenuItem = menuItem;
+
+    handlersMenuPopup.appendChild(this._selectedAppMenuItem);
 
     // List the default feed reader
     try {
       this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"].
                                   getService(Ci.nsIShellService).
                                   defaultFeedReader;
       menuItem = liveBookmarksMenuItem.cloneNode(false);
       menuItem.removeAttribute("selected");
@@ -1012,36 +936,32 @@ FeedWriter.prototype = {
       // as the last-selected application
       if (this._selectedApp &&
           this._selectedApp.path == this._defaultSystemReader.path)
         menuItem.hidden = true;
     }
     catch(ex) { menuItem = null; /* no default reader */ }
 
     if (menuItem) {
-      this._contentSandbox.defaultHandlerMenuItem = menuItem;
-      codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
+      this._defaultHandlerMenuItem = menuItem;
+      handlersMenuPopup.appendChild(this._defaultHandlerMenuItem);
     }
 
     // "Choose Application..." menuitem
     menuItem = liveBookmarksMenuItem.cloneNode(false);
     menuItem.removeAttribute("selected");
     menuItem.setAttribute("anonid", "chooseApplicationMenuItem");
     menuItem.className = "menuitem-iconic chooseApplicationMenuItem";
     menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
 
-    this._contentSandbox.chooseAppMenuItem = menuItem;
-    codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
+    handlersMenuPopup.appendChild(menuItem);
 
     // separator
-    this._contentSandbox.chooseAppSep =
-      menuItem = liveBookmarksMenuItem.nextSibling.cloneNode(false);
-    codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
-
-    Cu.evalInSandbox(codeStr, this._contentSandbox);
+    var chooseAppSep = liveBookmarksMenuItem.nextSibling.cloneNode(false);
+    handlersMenuPopup.appendChild(chooseAppSep);
 
     // List of web handlers
     var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
                getService(Ci.nsIWebContentConverterService);
     var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
     if (handlers.length != 0) {
       for (var i = 0; i < handlers.length; ++i) {
         if (!handlers[i].uri) {
@@ -1049,23 +969,20 @@ FeedWriter.prototype = {
           continue;
         }
         menuItem = liveBookmarksMenuItem.cloneNode(false);
         menuItem.removeAttribute("selected");
         menuItem.className = "menuitem-iconic";
         menuItem.setAttribute("label", handlers[i].name);
         menuItem.setAttribute("handlerType", "web");
         menuItem.setAttribute("webhandlerurl", handlers[i].uri);
-        this._contentSandbox.menuItem = menuItem;
-        codeStr = "handlersMenuPopup.appendChild(menuItem);";
-        Cu.evalInSandbox(codeStr, this._contentSandbox);
+        handlersMenuPopup.appendChild(menuItem);
 
         this._setFaviconForWebReader(handlers[i].uri, menuItem);
       }
-      this._contentSandbox.menuItem = null;
     }
 
     this._setSelectedHandler(feedType);
 
     // "Subscribe using..."
     this._setSubscribeUsingLabel();
 
     // "Always use..." checkbox initial state
@@ -1097,27 +1014,26 @@ FeedWriter.prototype = {
           textfeedinfo1 = "feedSubscriptionAudioPodcast1";
           textfeedinfo2 = "feedSubscriptionAudioPodcast2";
           break;
         default:
           textfeedinfo1 = "feedSubscriptionFeed1";
           textfeedinfo2 = "feedSubscriptionFeed2";
       }
 
-      this._contentSandbox.feedinfo1 =
-        this._document.getElementById("feedSubscriptionInfo1");
-      this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
-      this._contentSandbox.feedinfo2 =
-        this._document.getElementById("feedSubscriptionInfo2");
-      this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
-      this._contentSandbox.header = header;
-      codeStr = "feedinfo1.textContent = feedinfo1Str; " +
-                "feedinfo2.textContent = feedinfo2Str; " +
-                "header.setAttribute('firstrun', 'true');"
-      Cu.evalInSandbox(codeStr, this._contentSandbox);
+      var feedinfo1 = this._document.getElementById("feedSubscriptionInfo1");
+      var feedinfo1Str = this._getString(textfeedinfo1);
+      var feedinfo2 = this._document.getElementById("feedSubscriptionInfo2");
+      var feedinfo2Str = this._getString(textfeedinfo2);
+
+      feedinfo1.textContent = feedinfo1Str;
+      feedinfo2.textContent = feedinfo2Str;
+
+      header.setAttribute('firstrun', 'true');
+
       prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
     }
   },
 
   /**
    * Returns the original URI object of the feed and ensures that this
    * component is only ever invoked from the preview document.  
    * @param aWindow 
@@ -1232,17 +1148,20 @@ FeedWriter.prototype = {
     prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
     prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
     prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
 
     this._removeFeedFromCache();
     this.__faviconService = null;
     this.__bundle = null;
     this._feedURI = null;
-    this.__contentSandbox = null;
+
+    this._selectedApp = undefined;
+    this._selectedAppMenuItem = null;
+    this._defaultHandlerMenuItem = null;
   },
 
   _removeFeedFromCache: function FW__removeFeedFromCache() {
     if (this._feedURI) {
       var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
                         getService(Ci.nsIFeedResultService);
       feedService.removeFeedResult(this._feedURI);
       this._feedURI = null;
@@ -1391,22 +1310,17 @@ FeedWriter.prototype = {
                                          .usePrivateBrowsing;
     this._faviconService.setAndFetchFaviconForPage(readerURI, faviconURI, false,
       usePrivateBrowsing ? this._faviconService.FAVICON_LOAD_PRIVATE
                          : this._faviconService.FAVICON_LOAD_NON_PRIVATE,
       function (aURI, aDataLen, aData, aMimeType) {
         if (aDataLen > 0) {
           var dataURL = "data:" + aMimeType + ";base64," +
                         btoa(String.fromCharCode.apply(null, aData));
-          self._contentSandbox.menuItem = aMenuItem;
-          self._contentSandbox.dataURL = dataURL;
-          var codeStr = "menuItem.setAttribute('image', dataURL);";
-          Cu.evalInSandbox(codeStr, self._contentSandbox);
-          self._contentSandbox.menuItem = null;
-          self._contentSandbox.dataURL = null;
+          aMenuItem.setAttribute('image', dataURL);
         }
       });
   },
 
   classID: FEEDWRITER_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver,
                                          Ci.nsINavHistoryObserver,
                                          Ci.nsIDOMGlobalPropertyInitializer])
--- 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/caps/nsNullPrincipalURI.cpp
+++ b/caps/nsNullPrincipalURI.cpp
@@ -269,22 +269,21 @@ nsNullPrincipalURI::CloneIgnoringRef(nsI
   // CloneIgnoringRef() is the same as Clone().
   return Clone(_newURI);
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::Equals(nsIURI *aOther, bool *_equals)
 {
   *_equals = false;
-  nsNullPrincipalURI *otherURI;
+  nsRefPtr<nsNullPrincipalURI> otherURI;
   nsresult rv = aOther->QueryInterface(kNullPrincipalURIImplementationCID,
-                                       (void **)&otherURI);
+                                       getter_AddRefs(otherURI));
   if (NS_SUCCEEDED(rv)) {
     *_equals = mPath == otherURI->mPath;
-    NS_RELEASE(otherURI);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipalURI::EqualsExceptRef(nsIURI *aOther, bool *_equals)
 {
   // GetRef/SetRef not supported by nsNullPrincipalURI, so
--- a/dom/apps/AppsService.js
+++ b/dom/apps/AppsService.js
@@ -161,13 +161,17 @@ AppsService.prototype = {
     // properly in child processes.
     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
       throw Cr.NS_ERROR_FAILURE;
     }
 
     return UserCustomizations.isFromExtension(aURI);
   },
 
+  updateDataStoreEntriesFromLocalId: function(aLocalId) {
+    return DOMApplicationRegistry.updateDataStoreEntriesFromLocalId(aLocalId);
+  },
+
   classID : APPS_SERVICE_CID,
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
--- a/dom/apps/AppsServiceChild.jsm
+++ b/dom/apps/AppsServiceChild.jsm
@@ -409,12 +409,16 @@ this.DOMApplicationRegistry = {
 
   getWebAppsBasePath: function getWebAppsBasePath() {
     debug("getWebAppsBasePath() not yet supported on child!");
     return null;
   },
 
   getAppInfo: function getAppInfo(aAppId) {
     return AppsUtils.getAppInfo(this.webapps, aAppId);
+  },
+
+  updateDataStoreEntriesFromLocalId: function(aLocalId) {
+    debug("updateDataStoreEntriesFromLocalId() not yet supported on child!");
   }
 }
 
 DOMApplicationRegistry.init();
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -4692,16 +4692,23 @@ this.DOMApplicationRegistry = {
   getCoreAppsBasePath: function() {
     return AppsUtils.getCoreAppsBasePath();
   },
 
   getWebAppsBasePath: function() {
     return OS.Path.dirname(this.appsFile);
   },
 
+  updateDataStoreEntriesFromLocalId: function(aLocalId) {
+    let app = appsService.getAppByLocalId(aLocalId);
+    if (app) {
+      this.updateDataStoreForApp(app.id);
+    }
+  },
+
   _isLaunchable: function(aApp) {
     if (this.allAppsLaunchable)
       return true;
 
     return WebappOSUtils.isLaunchable(aApp);
   },
 
   _notifyCategoryAndObservers: function(subject, topic, data,  msg) {
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -35,18 +35,18 @@ struct IMEState;
 enum nsLinkState {
   eLinkState_Unvisited  = 1,
   eLinkState_Visited    = 2,
   eLinkState_NotLink    = 3 
 };
 
 // IID for the nsIContent interface
 #define NS_ICONTENT_IID \
-{ 0x52cebfc8, 0x79ba, 0x4e38, \
-  { 0x8a, 0x4c, 0x7f, 0x9d, 0xb1, 0xa2, 0xb6, 0x1d } }
+{ 0x8e1bab9d, 0x8815, 0x4d2c, \
+  { 0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22 } }
 
 /**
  * A node of content in a document's content model. This interface
  * is supported by all content objects.
  */
 class nsIContent : public nsINode {
 public:
   typedef mozilla::widget::IMEState IMEState;
@@ -576,20 +576,22 @@ public:
 
   /**
    * The method focuses (or activates) element that accesskey is bound to. It is
    * called when accesskey is activated.
    *
    * @param aKeyCausesActivation - if true then element should be activated
    * @param aIsTrustedEvent - if true then event that is cause of accesskey
    *                          execution is trusted.
+   * @return true if the focus was changed.
    */
-  virtual void PerformAccesskey(bool aKeyCausesActivation,
+  virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent)
   {
+    return false;
   }
 
   /*
    * Get desired IME state for the content.
    *
    * @return The desired IME status for the content.
    *         This is a combination of an IME enabled value and
    *         an IME open value of widget::IMEState.
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -431,109 +431,31 @@ CSPAllowsInlineScript(nsIScriptElement *
   nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, false);
 
   if (!csp) {
     // no CSP --> allow
     return true;
   }
 
-  // An inline script can be allowed because all inline scripts are allowed,
-  // or else because it is whitelisted by a nonce-source or hash-source. This
-  // is a logical OR between whitelisting methods, so the allowInlineScript
-  // outparam can be reused for each check as long as we stop checking as soon
-  // as it is set to true. This also optimizes performance by avoiding the
-  // overhead of unnecessary checks.
-  bool allowInlineScript = true;
-  nsAutoTArray<unsigned short, 3> violations;
-
-  bool reportInlineViolation = false;
-  rv = csp->GetAllowsInlineScript(&reportInlineViolation, &allowInlineScript);
-  NS_ENSURE_SUCCESS(rv, false);
-  if (reportInlineViolation) {
-    violations.AppendElement(static_cast<unsigned short>(
-          nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT));
-  }
-
+  // query the nonce
+  nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
   nsAutoString nonce;
-  if (!allowInlineScript) {
-    nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
-    bool foundNonce = scriptContent->GetAttr(kNameSpaceID_None,
-                                             nsGkAtoms::nonce, nonce);
-    if (foundNonce) {
-      bool reportNonceViolation;
-      rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_SCRIPT,
-                               &reportNonceViolation, &allowInlineScript);
-      NS_ENSURE_SUCCESS(rv, false);
-      if (reportNonceViolation) {
-        violations.AppendElement(static_cast<unsigned short>(
-              nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT));
-      }
-    }
-  }
-
-  if (!allowInlineScript) {
-    bool reportHashViolation;
-    nsAutoString scriptText;
-    aElement->GetScriptText(scriptText);
-    rv = csp->GetAllowsHash(scriptText, nsIContentPolicy::TYPE_SCRIPT,
-                            &reportHashViolation, &allowInlineScript);
-    NS_ENSURE_SUCCESS(rv, false);
-    if (reportHashViolation) {
-      violations.AppendElement(static_cast<unsigned short>(
-            nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_SCRIPT));
-    }
-  }
+  scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
 
-  // What violation(s) should be reported?
-  //
-  // 1. If the script tag has a nonce attribute, and the nonce does not match
-  // the policy, report VIOLATION_TYPE_NONCE_SCRIPT.
-  // 2. If the policy has at least one hash-source, and the hashed contents of
-  // the script tag did not match any of them, report VIOLATION_TYPE_HASH_SCRIPT
-  // 3. Otherwise, report VIOLATION_TYPE_INLINE_SCRIPT if appropriate.
-  //
-  // 1 and 2 may occur together, 3 should only occur by itself. Naturally,
-  // every VIOLATION_TYPE_NONCE_SCRIPT and VIOLATION_TYPE_HASH_SCRIPT are also
-  // VIOLATION_TYPE_INLINE_SCRIPT, but reporting the
-  // VIOLATION_TYPE_INLINE_SCRIPT is redundant and does not help the developer.
-  if (!violations.IsEmpty()) {
-    MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
-               "How did we get any violations without an initial inline script violation?");
-    // gather information to log with violation report
-    nsIURI* uri = aDocument->GetDocumentURI();
-    nsAutoCString asciiSpec;
-    uri->GetAsciiSpec(asciiSpec);
-    nsAutoString scriptText;
-    aElement->GetScriptText(scriptText);
-    nsAutoString scriptSample(scriptText);
+  // query the scripttext
+  nsAutoString scriptText;
+  aElement->GetScriptText(scriptText);
 
-    // cap the length of the script sample at 40 chars
-    if (scriptSample.Length() > 40) {
-      scriptSample.Truncate(40);
-      scriptSample.AppendLiteral("...");
-    }
-
-    for (uint32_t i = 0; i < violations.Length(); i++) {
-      // Skip reporting the redundant inline script violation if there are
-      // other (nonce and/or hash violations) as well.
-      if (i > 0 || violations.Length() == 1) {
-        csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec),
-                                 scriptSample, aElement->GetScriptLineNumber(),
-                                 nonce, scriptText);
-      }
-    }
-  }
-
-  if (!allowInlineScript) {
-    NS_ASSERTION(!violations.IsEmpty(),
-        "CSP blocked inline script but is not reporting a violation");
-   return false;
-  }
-  return true;
+  bool allowInlineScript = false;
+  rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
+                            nonce, scriptText,
+                            aElement->GetScriptLineNumber(),
+                            &allowInlineScript);
+  return allowInlineScript;
 }
 
 bool
 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
 {
   // We need a document to evaluate scripts.
   NS_ENSURE_TRUE(mDocument, false);
 
--- a/dom/bindings/DOMJSClass.h
+++ b/dom/bindings/DOMJSClass.h
@@ -226,19 +226,19 @@ struct DOMJSClass
   // us which it is.
   const bool mDOMObjectIsISupports;
 
   const NativePropertyHooks* mNativeHooks;
 
   ParentGetter mGetParent;
   ProtoHandleGetter mGetProto;
 
-  // This stores the CC participant for the native, null if this class is for a
-  // worker or for a native inheriting from nsISupports (we can get the CC
-  // participant by QI'ing in that case).
+  // This stores the CC participant for the native, null if this class does not
+  // implement cycle collection or if it inherits from nsISupports (we can get
+  // the CC participant by QI'ing in that case).
   nsCycleCollectionParticipant* mParticipant;
 
   static const DOMJSClass* FromJSClass(const JSClass* base) {
     MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS);
     return reinterpret_cast<const DOMJSClass*>(base);
   }
 
   static const DOMJSClass* FromJSClass(const js::Class* base) {
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -943,17 +943,19 @@ CanvasRenderingContext2D::CanvasRenderin
 #endif
   // these are the default values from the Canvas spec
   , mWidth(0), mHeight(0)
   , mZero(false), mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
   , mDrawObserver(nullptr)
   , mIsEntireFrameInvalid(false)
-  , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
+  , mPredictManyRedrawCalls(false)
+  , mIsCapturedFrameInvalid(false)
+  , mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
 {
   sNumLivingContexts++;
 
   // The default is to use OpenGL mode
   if (!gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas()) {
     mRenderingMode = RenderingMode::SoftwareBackendMode;
   }
@@ -1048,16 +1050,17 @@ CanvasRenderingContext2D::Reset()
 
   // reset hit regions
   mHitRegionsOptions.ClearAndRetainStorage();
 
   // Since the target changes the backing texture will change, and this will
   // no longer be valid.
   mIsEntireFrameInvalid = false;
   mPredictManyRedrawCalls = false;
+  mIsCapturedFrameInvalid = false;
 
   return NS_OK;
 }
 
 void
 CanvasRenderingContext2D::SetStyleFromString(const nsAString& str,
                                              Style whichStyle)
 {
@@ -1106,16 +1109,18 @@ CanvasRenderingContext2D::StyleColorToSt
     aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
     aStr.Append(')');
   }
 }
 
 nsresult
 CanvasRenderingContext2D::Redraw()
 {
+  mIsCapturedFrameInvalid = true;
+
   if (mIsEntireFrameInvalid) {
     return NS_OK;
   }
 
   mIsEntireFrameInvalid = true;
 
   if (!mCanvasElement) {
     NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
@@ -1127,16 +1132,18 @@ CanvasRenderingContext2D::Redraw()
   mCanvasElement->InvalidateCanvasContent(nullptr);
 
   return NS_OK;
 }
 
 void
 CanvasRenderingContext2D::Redraw(const mgfx::Rect &r)
 {
+  mIsCapturedFrameInvalid = true;
+
   ++mInvalidateCount;
 
   if (mIsEntireFrameInvalid) {
     return;
   }
 
   if (mPredictManyRedrawCalls ||
     mInvalidateCount > kCanvasMaxInvalidateCount) {
@@ -1164,16 +1171,18 @@ CanvasRenderingContext2D::DidRefresh()
     auto gl = glue->GetGLContext();
     gl->FlushIfHeavyGLCallsSinceLastFlush();
   }
 }
 
 void
 CanvasRenderingContext2D::RedrawUser(const gfxRect& r)
 {
+  mIsCapturedFrameInvalid = true;
+
   if (mIsEntireFrameInvalid) {
     ++mInvalidateCount;
     return;
   }
 
   mgfx::Rect newr =
     mTarget->GetTransform().TransformBounds(ToRect(r));
   Redraw(newr);
@@ -5651,16 +5660,27 @@ CanvasRenderingContext2D::MarkContextCle
 {
   if (mInvalidateCount > 0) {
     mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount;
   }
   mIsEntireFrameInvalid = false;
   mInvalidateCount = 0;
 }
 
+void
+CanvasRenderingContext2D::MarkContextCleanForFrameCapture()
+{
+  mIsCapturedFrameInvalid = false;
+}
+
+bool
+CanvasRenderingContext2D::IsContextCleanForFrameCapture()
+{
+  return !mIsCapturedFrameInvalid;
+}
 
 bool
 CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager)
 {
   return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight));
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -455,16 +455,18 @@ public:
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   mozilla::layers::PersistentBufferProvider* GetBufferProvider(mozilla::layers::LayerManager* aManager);
   already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                CanvasLayer *aOldLayer,
                                                LayerManager *aManager) override;
   virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override;
   void MarkContextClean() override;
+  void MarkContextCleanForFrameCapture() override;
+  bool IsContextCleanForFrameCapture() override;
   NS_IMETHOD SetIsIPC(bool isIPC) override;
   // this rect is in canvas device space
   void Redraw(const mozilla::gfx::Rect &r);
   NS_IMETHOD Redraw(const gfxRect &r) override { Redraw(ToRect(r)); return NS_OK; }
   NS_IMETHOD SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions) override;
 
   /**
    * An abstract base class to be implemented by callers wanting to be notified
@@ -744,16 +746,23 @@ protected:
   bool mIsEntireFrameInvalid;
   /**
     * When this is set, the first call to Redraw(gfxRect) should set
     * mIsEntireFrameInvalid since we expect it will be followed by
     * many more Redraw calls.
     */
   bool mPredictManyRedrawCalls;
 
+  /**
+   * Flag to avoid unnecessary surface copies to FrameCaptureListeners in the
+   * case when the canvas is not currently being drawn into and not rendered
+   * but canvas capturing is still ongoing.
+   */
+  bool mIsCapturedFrameInvalid;
+
   // This is stored after GetThebesSurface has been called once to avoid
   // excessive ThebesSurface initialization overhead.
   nsRefPtr<gfxASurface> mThebesSurface;
 
   /**
     * We also have a device space pathbuilder. The reason for this is as
     * follows, when a path is being built, but the transform changes, we
     * can no longer keep a single path in userspace, considering there's
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -216,16 +216,17 @@ WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
     , mBypassShaderValidation(false)
     , mGLMaxSamples(1)
     , mNeedsFakeNoAlpha(false)
     , mNeedsFakeNoStencil(false)
 {
     mGeneration = 0;
     mInvalidated = false;
+    mCapturedFrameInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
 
     mActiveTexture = 0;
     mPixelStoreFlipY = false;
     mPixelStorePremultiplyAlpha = false;
     mPixelStoreColorspaceConversion = BROWSER_DEFAULT_WEBGL;
@@ -409,20 +410,22 @@ WebGLContext::DestroyResourcesAndContext
 #endif
 
     gl = nullptr;
 }
 
 void
 WebGLContext::Invalidate()
 {
-    if (mInvalidated)
+    if (!mCanvasElement)
         return;
 
-    if (!mCanvasElement)
+    mCapturedFrameInvalidated = true;
+
+    if (mInvalidated)
         return;
 
     nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
 
     mInvalidated = true;
     mCanvasElement->InvalidateCanvasContent(nullptr);
 }
 
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -311,16 +311,20 @@ public:
     already_AddRefed<CanvasLayer>
     GetCanvasLayer(nsDisplayListBuilder* builder, CanvasLayer* oldLayer,
                    LayerManager* manager) override;
 
     // Note that 'clean' here refers to its invalidation state, not the
     // contents of the buffer.
     void MarkContextClean() override { mInvalidated = false; }
 
+    void MarkContextCleanForFrameCapture() override { mCapturedFrameInvalidated = false; }
+
+    bool IsContextCleanForFrameCapture() override { return !mCapturedFrameInvalidated; }
+
     gl::GLContext* GL() const { return gl; }
 
     bool IsPremultAlpha() const { return mOptions.premultipliedAlpha; }
 
     bool IsPreservingDrawingBuffer() const { return mOptions.preserveDrawingBuffer; }
 
     bool PresentScreenBuffer();
 
@@ -1029,16 +1033,17 @@ protected:
         mMaxFetchedInstances = 0;
     }
 
     CheckedUint32 mGeneration;
 
     WebGLContextOptions mOptions;
 
     bool mInvalidated;
+    bool mCapturedFrameInvalidated;
     bool mResetLayer;
     bool mOptionsFrozen;
     bool mMinCapability;
     bool mDisableExtensions;
     bool mIsMesa;
     bool mLoseContextOnMemoryPressure;
     bool mCanLoseContextInForeground;
     bool mRestoreWhenVisible;
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -131,16 +131,23 @@ public:
 
   // Return true if the canvas should be forced to be "inactive" to ensure
   // it can be drawn to the screen even if it's too large to be blitted by
   // an accelerated CanvasLayer.
   virtual bool ShouldForceInactiveLayer(LayerManager *manager) { return false; }
 
   virtual void MarkContextClean() = 0;
 
+  // Called when a frame is captured.
+  virtual void MarkContextCleanForFrameCapture() = 0;
+
+  // Whether the context is clean or has been invalidated since the last frame
+  // was captured.
+  virtual bool IsContextCleanForFrameCapture() = 0;
+
   // Redraw the dirty rectangle of this canvas.
   NS_IMETHOD Redraw(const gfxRect &dirty) = 0;
 
   NS_IMETHOD SetContextOptions(JSContext* cx, JS::Handle<JS::Value> options) { return NS_OK; }
 
   // return true and fills in the bounding rect if elementis a child and has a hit region.
   virtual bool GetHitRegionRect(mozilla::dom::Element* element, nsRect& rect) { return false; }
 
--- a/dom/canvas/test/captureStream_common.js
+++ b/dom/canvas/test/captureStream_common.js
@@ -27,16 +27,30 @@ CaptureStreamTestHelper.prototype = {
   black: { data: [0, 0, 0, 255], name: "black" },
   green: { data: [0, 255, 0, 255], name: "green" },
   red: { data: [255, 0, 0, 255], name: "red" },
 
   /* Default element size for createAndAppendElement() */
   elemWidth: 100,
   elemHeight: 100,
 
+  /*
+   * Perform the drawing operation on each animation frame until stop is called
+   * on the returned object.
+   */
+  startDrawing: function (f) {
+    var stop = false;
+    var draw = () => {
+      f();
+      if (!stop) { window.requestAnimationFrame(draw); }
+    };
+    draw();
+    return { stop: () => stop = true };
+  },
+
   /* Request a frame from the stream played by |video|. */
   requestFrame: function (video) {
     info("Requesting frame from " + video.id);
     video.srcObject.requestFrame();
   },
 
   /* Tests the top left pixel of |video| against |refData|. Format [R,G,B,A]. */
   testPixel: function (video, refData, threshold) {
@@ -116,18 +130,18 @@ CaptureStreamTestHelper2D.prototype.clea
   var ctx = canvas.getContext('2d');
   ctx.clearRect(0, 0, canvas.width, canvas.height);
 };
 
 /* Draw the color |color| to the source canvas |canvas|. Format [R,G,B,A]. */
 CaptureStreamTestHelper2D.prototype.drawColor = function(canvas, color) {
   var ctx = canvas.getContext('2d');
   var rgba = color.data.slice(); // Copy to not overwrite the original array
+  rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1]
   info("Drawing color " + rgba.join(','));
-  rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1]
   ctx.fillStyle = "rgba(" + rgba.join(',') + ")";
 
   // Only fill top left corner to test that output is not flipped or rotated.
   ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2);
 };
 
 /* Test that the given 2d canvas is NOT origin-clean. */
 CaptureStreamTestHelper2D.prototype.testNotClean = function(canvas) {
--- a/dom/canvas/test/test_capture.html
+++ b/dom/canvas/test/test_capture.html
@@ -10,17 +10,17 @@
 <script>
 var c;       // Canvas element captured by streams.
 var h;       // CaptureStreamTestHelper holding utility test functions.
 var vauto;   // Video element with captureStream stream in automatic mode.
 var vmanual; // Video element with captureStream stream in manual (fps 0) mode.
 var vrate;   // Video element with captureStream stream with fixed frame rate.
 
 function checkDrawColorInitialRed() {
-  info("Checking that all video elements become red after first drawColor(red).");
+  info("Checking that all video elements become red when initiated just after the first drawColor(red).");
 
   h.drawColor(c, h.red);
 
   vauto.srcObject = c.captureStream();
   vmanual.srcObject = c.captureStream(0);
   vrate.srcObject = c.captureStream(10);
 
   ok(h.testPixel(vauto, [0, 0, 0, 0], 0), "vauto hould not be drawn to before stable state");
@@ -30,58 +30,62 @@ function checkDrawColorInitialRed() {
   return Promise.resolve()
     .then(() => h.waitForPixel(vauto, h.red, 0, "should become red automatically"))
     .then(() => h.waitForPixel(vrate, h.red, 0, "should become red automatically"))
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should become red when we get" +
                                                " to stable state (first frame)"));
 }
 
 function checkDrawColorGreen() {
-  info("Checking that drawColor(green) propagates properly to video elements.");
-  h.drawColor(c, h.green);
+  info("Checking that drawing green propagates properly to video elements.");
+
+  var drawing = h.startDrawing(() => h.drawColor(c, h.green));
 
   return Promise.resolve()
     .then(() => h.waitForPixel(vauto, h.green, 0, "should become green automatically"))
     .then(() => h.waitForPixel(vrate, h.green, 0, "should become green automatically"))
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should still be red"))
     .then(() => h.requestFrame(vmanual))
-    .then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after requstFrame()"));
+    .then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after requstFrame()"))
+    .catch(err => ok(false, "checkDrawColorGreen failed: ", err))
+    .then(() => drawing.stop());
 }
 
 function checkRequestFrameOrderGuarantee() {
-  info("Checking that requestFrame() immediately before and after drawColor() " +
-       "calls results in the expected frame seen in the stream.");
+  info("Checking that requestFrame() immediately after a drawColor() " +
+       "call results in the expected frame seen in the stream.");
 
   return Promise.resolve()
     .then(() => h.waitForPixel(vmanual, h.green, 0, "should still be green"))
     .then(() => h.drawColor(c, h.red))   // 1. Draw canvas red
     .then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame
-    .then(() => h.drawColor(c, h.green)) // 3. Immediately draw canvas green
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should become red after call order test"))
-    .then(() => h.waitForPixelToTimeout(vmanual, h.green, 0, 500, "should not become green after call order test"));
 }
 
 function checkDrawImageNotCleanRed() {
   info("Checking that drawImage with not origin-clean image renders streams useless.");
   var ctx = c.getContext('2d');
   var notCleanRed = new Image();
+  var drawing;
 
   return new Promise((resolve, reject) => {
     notCleanRed.onload = resolve;
     notCleanRed.onerror = () => reject(new Error("Failed to load tainted image."));
     notCleanRed.src = "http://example.com/tests/dom/canvas/test/image_red_crossorigin_credentials.png";
     document.body.appendChild(notCleanRed);
   })
-    .then(() => ctx.drawImage(notCleanRed, 0, 0, c.width, c.height))
+    .then(() => drawing = h.startDrawing(() => ctx.drawImage(notCleanRed, 0, 0, c.width, c.height)))
     .then(() => h.testNotClean(c))
     .then(() => h.waitForPixelToTimeout(vauto, h.red, 0, 1000, "should not become red"))
     .then(() => h.waitForPixelToTimeout(vrate, h.red, 0, 0, "should not become red"))
     .then(() => h.waitForPixel(vmanual, h.green, 0, "should still be green"))
     .then(() => h.requestFrame(vmanual))
-    .then(() => h.waitForPixelToTimeout(vmanual, h.red, 0, 1000, "should not become red"));
+    .then(() => h.waitForPixelToTimeout(vmanual, h.red, 0, 1000, "should not become red"))
+    .catch(err => ok(false, "checkDrawImageNotCleanRed failed: ", err))
+    .then(() => drawing.stop());
 }
 
 function finish() {
   ok(true, 'Test complete.');
   SimpleTest.finish();
 }
 
 function beginTest() {
--- a/dom/canvas/test/webgl-mochitest/test_capture.html
+++ b/dom/canvas/test/webgl-mochitest/test_capture.html
@@ -60,84 +60,64 @@ function checkClearColorInitialRed() {
 
   return Promise.resolve()
     .then(() => h.waitForPixel(vauto, h.red, 0, "should become red automatically"))
     .then(() => h.waitForPixel(vrate, h.red, 0, "should become red automatically"))
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should become red when we get to stable state (first frame)"))
 }
 
 function checkDrawColorGreen() {
-  info("Checking that drawColor() results in green frames.");
-  h.drawColor(c, h.green);
+  info("Checking that drawing green results in green video frames.");
+  var drawing = h.startDrawing(h.drawColor.bind(h, c, h.green));
   checkGLError('after DrawColor');
   return Promise.resolve()
     .then(() => h.waitForPixel(vauto, h.green, 0, "should become green automatically"))
     .then(() => h.waitForPixel(vrate, h.green, 0, "should become green automatically"))
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should still be red"))
     .then(() => h.requestFrame(vmanual))
     .then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after requstFrame()"))
+    .then(() => drawing.stop());
 }
 
 function checkClearColorRed() {
   info("Checking that clearing to red works.");
-  h.clearColor(c, h.red);
+  var drawing = h.startDrawing(h.clearColor.bind(h, c, h.red));
   return Promise.resolve()
     .then(() => h.waitForPixel(vauto, h.red, 0, "should become red automatically"))
     .then(() => h.waitForPixel(vrate, h.red, 0, "should become red automatically"))
     .then(() => h.waitForPixel(vmanual, h.green, 0, "should still be green"))
     .then(() => h.requestFrame(vmanual))
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should become red after requestFrame()"))
+    .then(() => drawing.stop());
 }
 
 function checkRequestFrameOrderGuarantee() {
-  info("Checking that requestFrame() immediately before and after draw " +
-       "calls results in the expected frame seen in the stream.");
+  info("Checking that requestFrame() immediately after a draw " +
+       "call results in the expected frame seen in the stream.");
   return Promise.resolve()
     .then(() => h.waitForPixel(vmanual, h.red, 0, "should still be red"))
     .then(() => h.drawColor(c, h.green)) // 1. Draw canvas green
     .then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame
-    .then(() => h.clearColor(c, h.red))  // 3. Immediately clear to red
     .then(() => h.waitForPixel(vmanual, h.green, 0, "should become green after call order test"))
-    .then(() => h.waitForPixelToTimeout(vmanual, h.red, 0, 500, "should not become red after call order test"));
-}
-
-function checkCapturingForbidden() {
-  info("Checking that capturing a WebGL context with " +
-       "`preservDrawingBuffer: false` is forbidden.");
-  var c2 = h.createAndAppendElement("canvas", "c2");
-  var gl2 = WebGLUtil.getWebGL("c2", false, { preserveDrawingBuffer: false });
-
-  var checkThrows = function(f, expected, fName) {
-    try {
-      f();
-      ok(false, fName + " should throw when not preserving drawing buffer");
-    } catch(e) {
-      is(e.name, expected, fName + " forbidden when not preserving drawing buffer");
-    }
-  };
-
-  checkThrows(() => c2.captureStream(), "NS_ERROR_FAILURE", "captureStream()");
-  checkThrows(() => c2.captureStream(0), "NS_ERROR_FAILURE", "captureStream(0)");
-  checkThrows(() => c2.captureStream(10), "NS_ERROR_FAILURE", "captureStream(10)");
 }
 
 function finish() {
   ok(true, 'Test complete.');
   SimpleTest.finish();
 }
 
 function beginTest() {
   h = new CaptureStreamTestHelperWebGL();
 
   c = h.createAndAppendElement('canvas', 'c');
   vauto = h.createAndAppendElement('video', 'vauto');
   vmanual = h.createAndAppendElement('video', 'vmanual');
   vrate = h.createAndAppendElement('video', 'vrate');
 
-  gl = WebGLUtil.getWebGL('c', false, { preserveDrawingBuffer: true });
+  gl = WebGLUtil.getWebGL('c', false);
   if (!gl) {
     todo(false, 'WebGL is unavailable.');
     finish();
     return;
   }
 
   function errorFunc(str) {
     ok(false, 'Error: ' + str);
@@ -181,17 +161,16 @@ function beginTest() {
 
   // Run tests.
 
   Promise.resolve()
     .then(checkClearColorInitialRed)
     .then(checkDrawColorGreen)
     .then(checkClearColorRed)
     .then(checkRequestFrameOrderGuarantee)
-    .then(checkCapturingForbidden)
     .then(finish);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 var prefs = [
   [ "canvas.capturestream.enabled", true ],
 ];
--- a/dom/datastore/DataStoreService.cpp
+++ b/dom/datastore/DataStoreService.cpp
@@ -120,16 +120,17 @@ public:
   // A DataStore is enabled when it has its first revision.
   bool mEnabled;
 };
 
 namespace {
 
 // Singleton for DataStoreService.
 StaticRefPtr<DataStoreService> gDataStoreService;
+nsString gHomeScreenManifestURL;
 static uint64_t gCounterID = 0;
 
 typedef nsClassHashtable<nsUint32HashKey, DataStoreInfo> HashApp;
 
 void
 RejectPromise(nsPIDOMWindow* aWindow, Promise* aPromise, nsresult aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -444,16 +445,28 @@ AddAccessPermissionsEnumerator(const uin
   bool readOnly = aInfo->mReadOnly || data->mReadOnly;
 
   data->mResult = ResetPermission(data->mAppId, data->mOriginURL,
                                   aInfo->mManifestURL,
                                   permission, readOnly);
   return NS_FAILED(data->mResult) ? PL_DHASH_STOP : PL_DHASH_NEXT;
 }
 
+void
+HomeScreenPrefCallback(const char* aPrefName, void* /* aClosure */)
+{
+  MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+  nsRefPtr<DataStoreService> service = DataStoreService::Get();
+  if (!service) {
+    return;
+  }
+
+  service->HomeScreenPrefChanged();
+}
+
 } /* anonymous namespace */
 
 // A PendingRequest is created when a content code wants a list of DataStores
 // but some of them are not enabled yet.
 class PendingRequest
 {
 public:
   void Init(nsPIDOMWindow* aWindow, Promise* aPromise,
@@ -800,16 +813,22 @@ DataStoreService::Shutdown()
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gDataStoreService) {
     if (XRE_IsParentProcess()) {
       nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
       if (obs) {
         obs->RemoveObserver(gDataStoreService, "webapps-clear-data");
       }
+
+      nsresult rv =
+        Preferences::UnregisterCallback(HomeScreenPrefCallback,
+                                        "dom.mozApps.homescreenURL",
+                                        nullptr);
+      NS_WARN_IF(NS_FAILED(rv));
     }
 
     gDataStoreService = nullptr;
   }
 }
 
 NS_INTERFACE_MAP_BEGIN(DataStoreService)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDataStoreService)
@@ -842,16 +861,23 @@ DataStoreService::Init()
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = obs->AddObserver(this, "webapps-clear-data", false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = Preferences::RegisterCallback(HomeScreenPrefCallback,
+                                     "dom.mozApps.homescreenURL",
+                                     nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DataStoreService::InstallDataStore(uint32_t aAppId,
                                    const nsAString& aName,
                                    const nsAString& aOriginURL,
                                    const nsAString& aManifestURL,
@@ -1490,10 +1516,95 @@ DataStoreService::GenerateUUID(nsAString
 
   char chars[NSID_LENGTH];
   id.ToProvidedString(chars);
   CopyASCIItoUTF16(chars, aID);
 
   return NS_OK;
 }
 
+void
+DataStoreService::HomeScreenPrefChanged()
+{
+  MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+  nsAdoptingString homescreen =
+    Preferences::GetString("dom.mozApps.homescreenURL");
+  if (homescreen == gHomeScreenManifestURL) {
+    return;
+  }
+
+  // Remove datastores of the old homescreen.
+  if (!gHomeScreenManifestURL.IsEmpty()) {
+    DeleteDataStoresIfNotAllowed(gHomeScreenManifestURL);
+  }
+
+  gHomeScreenManifestURL = homescreen;
+  if (gHomeScreenManifestURL.IsEmpty()) {
+    return;
+  }
+
+  // Add datastores for the new homescreen.
+  AddDataStoresIfAllowed(gHomeScreenManifestURL);
+}
+
+void
+DataStoreService::DeleteDataStoresIfNotAllowed(const nsAString& aManifestURL)
+{
+  nsCOMPtr<nsIAppsService> appsService =
+    do_GetService("@mozilla.org/AppsService;1");
+  if (NS_WARN_IF(!appsService)) {
+    return;
+  }
+
+  nsCOMPtr<mozIApplication> app;
+  nsresult rv = appsService->GetAppByManifestURL(aManifestURL,
+                                                 getter_AddRefs(app));
+  if (NS_WARN_IF(NS_FAILED(rv)) || !app) {
+    return;
+  }
+
+  uint32_t localId;
+  rv = app->GetLocalId(&localId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal;
+  rv = app->GetPrincipal(getter_AddRefs(principal));
+
+  // We delete all the dataStores for this app here.
+  if (NS_WARN_IF(NS_FAILED(rv)) || !principal ||
+      !CheckPermission(principal)) {
+    DeleteDataStores(localId);
+  }
+}
+
+void
+DataStoreService::AddDataStoresIfAllowed(const nsAString& aManifestURL)
+{
+  nsCOMPtr<nsIAppsService> appsService =
+    do_GetService("@mozilla.org/AppsService;1");
+  if (NS_WARN_IF(!appsService)) {
+    return;
+  }
+
+  nsCOMPtr<mozIApplication> app;
+  nsresult rv = appsService->GetAppByManifestURL(aManifestURL,
+                                                 getter_AddRefs(app));
+  if (NS_WARN_IF(NS_FAILED(rv)) || !app) {
+    return;
+  }
+
+  uint32_t localId;
+  rv = app->GetLocalId(&localId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  rv = appsService->UpdateDataStoreEntriesFromLocalId(localId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/datastore/DataStoreService.h
+++ b/dom/datastore/DataStoreService.h
@@ -52,16 +52,18 @@ public:
 
   nsresult GenerateUUID(nsAString& aID);
 
   nsresult GetDataStoresFromIPC(const nsAString& aName,
                                 const nsAString& aOwner,
                                 nsIPrincipal* aPrincipal,
                                 nsTArray<DataStoreSetting>* aValue);
 
+  void HomeScreenPrefChanged();
+
 private:
   DataStoreService();
   ~DataStoreService();
 
   nsresult Init();
 
   typedef nsClassHashtable<nsUint32HashKey, DataStoreInfo> HashApp;
 
@@ -92,16 +94,19 @@ private:
 
   nsresult EnableDataStore(uint32_t aAppId, const nsAString& aName,
                            const nsAString& aManifestURL);
 
   already_AddRefed<RetrieveRevisionsCounter> GetCounter(uint32_t aId) const;
 
   void RemoveCounter(uint32_t aId);
 
+  void DeleteDataStoresIfNotAllowed(const nsAString& aManifestURL);
+  void AddDataStoresIfAllowed(const nsAString& aManifestURL);
+
   nsClassHashtable<nsStringHashKey, HashApp> mStores;
   nsClassHashtable<nsStringHashKey, HashApp> mAccessStores;
 
   typedef nsTArray<PendingRequest> PendingRequests;
   nsClassHashtable<nsStringHashKey, PendingRequests> mPendingRequests;
 
   nsRefPtrHashtable<nsUint32HashKey, RetrieveRevisionsCounter> mPendingCounters;
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -717,52 +717,47 @@ EventListenerManager::SetEventHandler(ns
   // 'doc' is fetched above
   if (doc) {
     // Don't allow adding an event listener if the document is sandboxed
     // without 'allow-scripts'.
     if (doc->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
+    // Perform CSP check
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (csp) {
-      bool inlineOK = true;
-      bool reportViolations = false;
-      rv = csp->GetAllowsInlineScript(&reportViolations, &inlineOK);
+      // let's generate a script sample and pass it as aContent,
+      // it will not match the hash, but allows us to pass
+      // the script sample in aCOntent.
+      nsAutoString scriptSample, attr, tagName(NS_LITERAL_STRING("UNKNOWN"));
+      aName->ToString(attr);
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mTarget));
+      if (domNode) {
+        domNode->GetNodeName(tagName);
+      }
+      // build a "script sample" based on what we know about this element
+      scriptSample.Assign(attr);
+      scriptSample.AppendLiteral(" attribute on ");
+      scriptSample.Append(tagName);
+      scriptSample.AppendLiteral(" element");
+
+      bool allowsInlineScript = true;
+      rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
+                                EmptyString(), // aNonce
+                                scriptSample,
+                                0,             // aLineNumber
+                                &allowsInlineScript);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (reportViolations) {
-        // gather information to log with violation report
-        nsIURI* uri = doc->GetDocumentURI();
-        nsAutoCString asciiSpec;
-        if (uri)
-          uri->GetAsciiSpec(asciiSpec);
-        nsAutoString scriptSample, attr, tagName(NS_LITERAL_STRING("UNKNOWN"));
-        aName->ToString(attr);
-        nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mTarget));
-        if (domNode)
-          domNode->GetNodeName(tagName);
-        // build a "script sample" based on what we know about this element
-        scriptSample.Assign(attr);
-        scriptSample.AppendLiteral(" attribute on ");
-        scriptSample.Append(tagName);
-        scriptSample.AppendLiteral(" element");
-        csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
-                                 NS_ConvertUTF8toUTF16(asciiSpec),
-                                 scriptSample,
-                                 0,
-                                 EmptyString(),
-                                 EmptyString());
-      }
-
       // return early if CSP wants us to block inline scripts
-      if (!inlineOK) {
+      if (!allowsInlineScript) {
         return NS_OK;
       }
     }
   }
 
   // This might be the first reference to this language in the global
   // We must init the language before we attempt to fetch its context.
   if (NS_FAILED(global->EnsureScriptEnvironment())) {
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -44,16 +44,17 @@
 #include "nsISelection.h"
 #include "nsITextControlElement.h"
 #include "nsFrameSelection.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsIWebNavigation.h"
 #include "nsIContentViewer.h"
 #include "nsFrameManager.h"
+#include "nsITabChild.h"
 
 #include "nsIDOMXULElement.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIObserverService.h"
 #include "nsIDocShell.h"
 #include "nsIDOMWheelEvent.h"
 #include "nsIDOMUIEvent.h"
 #include "nsIMozBrowserFrame.h"
@@ -679,18 +680,23 @@ EventStateManager::PreHandleEvent(nsPres
         modifierMask |= NS_MODIFIER_META;
       if (keyEvent->IsOS())
         modifierMask |= NS_MODIFIER_OS;
 
       // Prevent keyboard scrolling while an accesskey modifier is in use.
       if (modifierMask &&
           (modifierMask == Prefs::ChromeAccessModifierMask() ||
            modifierMask == Prefs::ContentAccessModifierMask())) {
-        HandleAccessKey(aPresContext, keyEvent, aStatus, nullptr,
-                        eAccessKeyProcessingNormal, modifierMask);
+        nsAutoTArray<uint32_t, 10> accessCharCodes;
+        nsContentUtils::GetAccessKeyCandidates(keyEvent, accessCharCodes);
+
+        if (HandleAccessKey(aPresContext, accessCharCodes,
+                            keyEvent->mFlags.mIsTrusted, modifierMask)) {
+          *aStatus = nsEventStatus_eConsumeNoDefault;
+        }
       }
     }
     // then fall through...
   case eBeforeKeyDown:
   case eKeyDown:
   case eAfterKeyDown:
   case eBeforeKeyUp:
   case eKeyUp:
@@ -904,25 +910,38 @@ EventStateManager::ExecuteAccessKey(nsTA
       if (IsAccessKeyTarget(content, frame, accessKey)) {
         bool shouldActivate = Prefs::KeyCausesActivation();
         while (shouldActivate && ++count <= length) {
           nsIContent *oc = mAccessKeys[(start + count) % length];
           nsIFrame *of = oc->GetPrimaryFrame();
           if (IsAccessKeyTarget(oc, of, accessKey))
             shouldActivate = false;
         }
-        if (shouldActivate)
-          content->PerformAccesskey(shouldActivate, aIsTrustedEvent);
-        else {
+
+        bool focusChanged = false;
+        if (shouldActivate) {
+          focusChanged = content->PerformAccesskey(shouldActivate, aIsTrustedEvent);
+        } else {
           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
           if (fm) {
             nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
             fm->SetFocus(element, nsIFocusManager::FLAG_BYKEY);
+            focusChanged = true;
           }
         }
+
+        if (focusChanged && aIsTrustedEvent) {
+          // If this is a child process, inform the parent that we want the focus, but
+          // pass false since we don't want to change the window order.
+          nsCOMPtr<nsITabChild> child = do_GetInterface(mPresContext->GetDocShell());
+          if (child) {
+            child->SendRequestFocus(false);
+          }
+        }
+
         return true;
       }
     }
   }
   return false;
 }
 
 // static
@@ -957,108 +976,139 @@ EventStateManager::GetAccessKeyLabelPref
     aPrefix.Append(modifierText + separator);
   }
   if (modifierMask & NS_MODIFIER_SHIFT) {
     nsContentUtils::GetShiftText(modifierText);
     aPrefix.Append(modifierText + separator);
   }
 }
 
-void
+struct AccessKeyInfo
+{
+  nsTArray<uint32_t>& charCodes;
+  bool isTrusted;
+  int32_t modifierMask;
+
+  AccessKeyInfo(nsTArray<uint32_t>& aCharCodes, bool aIsTrusted, int32_t aModifierMask)
+    : charCodes(aCharCodes)
+    , isTrusted(aIsTrusted)
+    , modifierMask(aModifierMask)
+  {
+  }
+};
+
+static void
+HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg)
+{
+  AccessKeyInfo* accessKeyInfo = static_cast<AccessKeyInfo*>(aArg);
+
+  // Only forward accesskeys for the active tab.
+  bool active;
+  aTabParent->GetDocShellIsActive(&active);
+  if (active) {
+    aTabParent->HandleAccessKey(accessKeyInfo->charCodes, accessKeyInfo->isTrusted,
+                                accessKeyInfo->modifierMask);
+  }
+}
+
+bool
 EventStateManager::HandleAccessKey(nsPresContext* aPresContext,
-                                   WidgetKeyboardEvent* aEvent,
-                                   nsEventStatus* aStatus,
+                                   nsTArray<uint32_t>& aAccessCharCodes,
+                                   bool aIsTrusted,
                                    nsIDocShellTreeItem* aBubbledFrom,
                                    ProcessingAccessKeyState aAccessKeyState,
                                    int32_t aModifierMask)
 {
+  EnsureDocument(mPresContext);
   nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
-
-  // Alt or other accesskey modifier is down, we may need to do an accesskey
+  if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
+    return false;
+  }
+
+  // Alt or other accesskey modifier is down, we may need to do an accesskey.
   if (mAccessKeys.Count() > 0 &&
       aModifierMask == GetAccessModifierMaskFor(docShell)) {
     // Someone registered an accesskey.  Find and activate it.
-    nsAutoTArray<uint32_t, 10> accessCharCodes;
-    nsContentUtils::GetAccessKeyCandidates(aEvent, accessCharCodes);
-    if (ExecuteAccessKey(accessCharCodes, aEvent->mFlags.mIsTrusted)) {
-      *aStatus = nsEventStatus_eConsumeNoDefault;
-      return;
+    if (ExecuteAccessKey(aAccessCharCodes, aIsTrusted)) {
+      return true;
     }
   }
 
-  // after the local accesskey handling
-  if (nsEventStatus_eConsumeNoDefault != *aStatus) {
-    // checking all sub docshells
-
-    if (!docShell) {
-      NS_WARNING("no docShellTreeNode for presContext");
-      return;
+  int32_t childCount;
+  docShell->GetChildCount(&childCount);
+  for (int32_t counter = 0; counter < childCount; counter++) {
+    // Not processing the child which bubbles up the handling
+    nsCOMPtr<nsIDocShellTreeItem> subShellItem;
+    docShell->GetChildAt(counter, getter_AddRefs(subShellItem));
+    if (aAccessKeyState == eAccessKeyProcessingUp &&
+        subShellItem == aBubbledFrom) {
+      continue;
     }
 
-    int32_t childCount;
-    docShell->GetChildCount(&childCount);
-    for (int32_t counter = 0; counter < childCount; counter++) {
-      // Not processing the child which bubbles up the handling
-      nsCOMPtr<nsIDocShellTreeItem> subShellItem;
-      docShell->GetChildAt(counter, getter_AddRefs(subShellItem));
-      if (aAccessKeyState == eAccessKeyProcessingUp &&
-          subShellItem == aBubbledFrom)
+    nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
+    if (subDS && IsShellVisible(subDS)) {
+      nsCOMPtr<nsIPresShell> subPS = subDS->GetPresShell();
+
+      // Docshells need not have a presshell (eg. display:none
+      // iframes, docshells in transition between documents, etc).
+      if (!subPS) {
+        // Oh, well.  Just move on to the next child
         continue;
-
-      nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
-      if (subDS && IsShellVisible(subDS)) {
-        nsCOMPtr<nsIPresShell> subPS = subDS->GetPresShell();
-
-        // Docshells need not have a presshell (eg. display:none
-        // iframes, docshells in transition between documents, etc).
-        if (!subPS) {
-          // Oh, well.  Just move on to the next child
-          continue;
-        }
-
-        nsPresContext *subPC = subPS->GetPresContext();
-
-        EventStateManager* esm =
-          static_cast<EventStateManager*>(subPC->EventStateManager());
-
-        if (esm)
-          esm->HandleAccessKey(subPC, aEvent, aStatus, nullptr,
-                               eAccessKeyProcessingDown, aModifierMask);
-
-        if (nsEventStatus_eConsumeNoDefault == *aStatus)
-          break;
+      }
+
+      nsPresContext *subPC = subPS->GetPresContext();
+
+      EventStateManager* esm =
+        static_cast<EventStateManager*>(subPC->EventStateManager());
+
+      if (esm &&
+          esm->HandleAccessKey(subPC, aAccessCharCodes, aIsTrusted, nullptr,
+                               eAccessKeyProcessingDown, aModifierMask)) {
+        return true;
       }
     }
   }// if end . checking all sub docshell ends here.
 
   // bubble up the process to the parent docshell if necessary
-  if (eAccessKeyProcessingDown != aAccessKeyState && nsEventStatus_eConsumeNoDefault != *aStatus) {
-    if (!docShell) {
-      NS_WARNING("no docShellTreeItem for presContext");
-      return;
-    }
-
+  if (eAccessKeyProcessingDown != aAccessKeyState) {
     nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
     docShell->GetParent(getter_AddRefs(parentShellItem));
     nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
     if (parentDS) {
       nsCOMPtr<nsIPresShell> parentPS = parentDS->GetPresShell();
       NS_ASSERTION(parentPS, "Our PresShell exists but the parent's does not?");
 
       nsPresContext *parentPC = parentPS->GetPresContext();
       NS_ASSERTION(parentPC, "PresShell without PresContext");
 
       EventStateManager* esm =
         static_cast<EventStateManager*>(parentPC->EventStateManager());
-
-      if (esm)
-        esm->HandleAccessKey(parentPC, aEvent, aStatus, docShell,
-                             eAccessKeyProcessingUp, aModifierMask);
+      if (esm &&
+          esm->HandleAccessKey(parentPC, aAccessCharCodes, aIsTrusted, docShell,
+                               eAccessKeyProcessingUp, aModifierMask)) {
+        return true;
+      }
     }
   }// if end. bubble up process
+
+  // Now try remote children
+  if (mDocument && mDocument->GetWindow()) {
+    // If the focus is currently on a node with a TabParent, the key event will
+    // get forwarded to the child process and HandleAccessKey called from there.
+    // If focus is somewhere else, then we need to check the remote children.
+    nsFocusManager* fm = nsFocusManager::GetFocusManager();
+    nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr;
+    if (!TabParent::GetFrom(focusedContent)) {
+      AccessKeyInfo accessKeyInfo(aAccessCharCodes, aIsTrusted, aModifierMask);
+      nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
+                                              HandleAccessKeyInRemoteChild, &accessKeyInfo);
+    }
+  }
+
+  return false;
 }// end of HandleAccessKey
 
 bool
 EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
                                              nsFrameLoader* aFrameLoader,
                                              nsEventStatus *aStatus) {
   TabParent* remote = TabParent::GetFrom(aFrameLoader);
   if (!remote) {
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -168,16 +168,25 @@ public:
    *
    * @param  aContent  the given element (must not be null)
    * @return           registered accesskey
    */
   uint32_t GetRegisteredAccessKey(nsIContent* aContent);
 
   static void GetAccessKeyLabelPrefix(dom::Element* aElement, nsAString& aPrefix);
 
+  bool HandleAccessKey(nsPresContext* aPresContext,
+                       nsTArray<uint32_t>& aAccessCharCodes,
+                       bool aIsTrusted,
+                       int32_t aModifierMask)
+  {
+    return HandleAccessKey(aPresContext, aAccessCharCodes, aIsTrusted,
+                           nullptr, eAccessKeyProcessingNormal, aModifierMask);
+  }
+
   nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
                      bool aHaveHotspot, float aHotspotX, float aHotspotY,
                      nsIWidget* aWidget, bool aLockCursor); 
 
   static void StartHandlingUserInput()
   {
     ++sUserInputEventDepth;
     if (sUserInputEventDepth == 1) {
@@ -383,33 +392,33 @@ protected:
   /**
    * Access key handling.  If there is registered content for the accesskey
    * given by the key event and modifier mask then call
    * content.PerformAccesskey(), otherwise call HandleAccessKey() recursively,
    * on descendant docshells first, then on the ancestor (with |aBubbledFrom|
    * set to the docshell associated with |this|), until something matches.
    *
    * @param aPresContext the presentation context
-   * @param aEvent the key event
-   * @param aStatus the event status
+   * @param aAccessCharCodes list of charcode candidates
+   * @param aIsTrusted true if triggered by a trusted key event
    * @param aBubbledFrom is used by an ancestor to avoid calling HandleAccessKey()
    *        on the child the call originally came from, i.e. this is the child
    *        that recursively called us in its Up phase. The initial caller
    *        passes |nullptr| here. This is to avoid an infinite loop.
    * @param aAccessKeyState Normal, Down or Up processing phase (see enums
    *        above). The initial event receiver uses 'normal', then 'down' when
    *        processing children and Up when recursively calling its ancestor.
    * @param aModifierMask modifier mask for the key event
    */
-  void HandleAccessKey(nsPresContext* aPresContext,
-                       WidgetKeyboardEvent* aEvent,
-                       nsEventStatus* aStatus,
-                       nsIDocShellTreeItem* aBubbledFrom,
-                       ProcessingAccessKeyState aAccessKeyState,
-                       int32_t aModifierMask);
+  bool HandleAccessKey(nsPresContext* aPresContext,
+                     nsTArray<uint32_t>& aAccessCharCodes,
+                     bool aIsTrusted,
+                     nsIDocShellTreeItem* aBubbledFrom,
+                     ProcessingAccessKeyState aAccessKeyState,
+                     int32_t aModifierMask);
 
   bool ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
                           bool aIsTrustedEvent);
 
   //---------------------------------------------
   // DocShell Focus Traversal Methods
   //---------------------------------------------
 
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/HTMLCanvasElement.h"
 
 #include "ImageEncoder.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "Layers.h"
+#include "MediaSegment.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Base64.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/CanvasCaptureMediaStream.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/HTMLCanvasElementBinding.h"
 #include "mozilla/dom/MouseEvent.h"
@@ -30,29 +31,160 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsITimer.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsMathUtils.h"
 #include "nsNetUtil.h"
+#include "nsRefreshDriver.h"
 #include "nsStreamUtils.h"
 #include "ActiveLayerTracker.h"
 #include "WebGL1Context.h"
 #include "WebGL2Context.h"
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
 
 namespace mozilla {
 namespace dom {
 
+class RequestedFrameRefreshObserver : public nsARefreshObserver
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestedFrameRefreshObserver, override)
+
+public:
+  RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
+                                nsRefreshDriver* aRefreshDriver)
+    : mRegistered(false),
+      mOwningElement(aOwningElement),
+      mRefreshDriver(aRefreshDriver)
+  {
+    MOZ_ASSERT(mOwningElement);
+  }
+
+  static already_AddRefed<DataSourceSurface>
+  CopySurface(const RefPtr<SourceSurface>& aSurface)
+  {
+    RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+    if (!data) {
+      return nullptr;
+    }
+
+    DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
+    if (!read.IsMapped()) {
+      return nullptr;
+    }
+
+    RefPtr<DataSourceSurface> copy =
+      Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
+                                                 data->GetFormat(),
+                                                 read.GetStride());
+    if (!copy) {
+      return nullptr;
+    }
+
+    DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
+    if (!write.IsMapped()) {
+      return nullptr;
+    }
+
+    MOZ_ASSERT(read.GetStride() == write.GetStride());
+    MOZ_ASSERT(data->GetSize() == copy->GetSize());
+    MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
+
+    memcpy(write.GetData(), read.GetData(),
+           write.GetStride() * copy->GetSize().height);
+
+    return copy.forget();
+  }
+
+  void WillRefresh(TimeStamp aTime) override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!mOwningElement) {
+      return;
+    }
+
+    if (mOwningElement->IsWriteOnly()) {
+      return;
+    }
+
+    if (mOwningElement->IsContextCleanForFrameCapture()) {
+      return;
+    }
+
+    if (!mOwningElement->IsFrameCaptureRequested()) {
+      return;
+    }
+
+    RefPtr<SourceSurface> snapshot = mOwningElement->GetSurfaceSnapshot(nullptr);
+    if (!snapshot) {
+      return;
+    }
+
+    RefPtr<DataSourceSurface> copy = CopySurface(snapshot);
+
+    mOwningElement->SetFrameCapture(copy.forget());
+    mOwningElement->MarkContextCleanForFrameCapture();
+  }
+
+  void DetachFromRefreshDriver()
+  {
+    MOZ_ASSERT(mOwningElement);
+    MOZ_ASSERT(mRefreshDriver);
+
+    Unregister();
+    mRefreshDriver = nullptr;
+  }
+
+  void Register()
+  {
+    if (mRegistered) {
+      return;
+    }
+
+    MOZ_ASSERT(mRefreshDriver);
+    if (mRefreshDriver) {
+      mRefreshDriver->AddRefreshObserver(this, Flush_Display);
+      mRegistered = true;
+    }
+  }
+
+  void Unregister()
+  {
+    if (!mRegistered) {
+      return;
+    }
+
+    MOZ_ASSERT(mRefreshDriver);
+    if (mRefreshDriver) {
+      mRefreshDriver->RemoveRefreshObserver(this, Flush_Display);
+      mRegistered = false;
+    }
+  }
+
+private:
+  virtual ~RequestedFrameRefreshObserver()
+  {
+    MOZ_ASSERT(!mRefreshDriver);
+    MOZ_ASSERT(!mRegistered);
+  }
+
+  bool mRegistered;
+  HTMLCanvasElement* const mOwningElement;
+  RefPtr<nsRefreshDriver> mRefreshDriver;
+};
+
+// ---------------------------------------------------------------------------
+
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas,
                                       mContext, mCallback)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLCanvasPrintState, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLCanvasPrintState, Release)
 
 HTMLCanvasPrintState::HTMLCanvasPrintState(HTMLCanvasElement* aCanvas,
                                            nsICanvasRenderingContextInternal* aContext,
@@ -111,16 +243,19 @@ HTMLCanvasElement::HTMLCanvasElement(alr
   : nsGenericHTMLElement(aNodeInfo),
     mWriteOnly(false)
 {
 }
 
 HTMLCanvasElement::~HTMLCanvasElement()
 {
   ResetPrintCallback();
+  if (mRequestedFrameRefreshObserver) {
+    mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
+  }
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
                                    mCurrentContext, mPrintCallback,
                                    mPrintState, mOriginalCanvas)
 
 NS_IMPL_ADDREF_INHERITED(HTMLCanvasElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLCanvasElement, Element)
@@ -414,40 +549,35 @@ HTMLCanvasElement::CaptureStream(const O
     return nullptr;
   }
 
   if (!mCurrentContext) {
     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
     return nullptr;
   }
 
-  if (mCurrentContextType != CanvasContextType::Canvas2D) {
-    WebGLContext* gl = static_cast<WebGLContext*>(mCurrentContext.get());
-    if (!gl->IsPreservingDrawingBuffer()) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return nullptr;
-    }
-  }
-
   nsRefPtr<CanvasCaptureMediaStream> stream =
     CanvasCaptureMediaStream::CreateSourceStream(window, this);
   if (!stream) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<nsIPrincipal> principal = NodePrincipal();
   stream->CombineWithPrincipal(principal);
 
   TrackID videoTrackId = 1;
   nsresult rv = stream->Init(aFrameRate, videoTrackId);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
+
+  stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO);
+  RegisterFrameCaptureListener(stream->FrameCaptureListener());
   return stream.forget();
 }
 
 nsresult
 HTMLCanvasElement::ExtractData(nsAString& aType,
                                const nsAString& aOptions,
                                nsIInputStream** aStream)
 {
@@ -1027,16 +1157,110 @@ void
 HTMLCanvasElement::MarkContextClean()
 {
   if (!mCurrentContext)
     return;
 
   mCurrentContext->MarkContextClean();
 }
 
+void
+HTMLCanvasElement::MarkContextCleanForFrameCapture()
+{
+  if (!mCurrentContext)
+    return;
+
+  mCurrentContext->MarkContextCleanForFrameCapture();
+}
+
+bool
+HTMLCanvasElement::IsContextCleanForFrameCapture()
+{
+  return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
+}
+
+void
+HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
+{
+  WeakPtr<FrameCaptureListener> listener = aListener;
+
+  if (mRequestedFrameListeners.Contains(listener)) {
+    return;
+  }
+
+  mRequestedFrameListeners.AppendElement(listener);
+
+  if (!mRequestedFrameRefreshObserver) {
+    nsIDocument* doc = OwnerDoc();
+    MOZ_RELEASE_ASSERT(doc);
+
+    nsIPresShell* shell = doc->GetShell();
+    MOZ_RELEASE_ASSERT(shell);
+
+    nsPresContext* context = shell->GetPresContext();
+    MOZ_RELEASE_ASSERT(context);
+
+    context = context->GetRootPresContext();
+    MOZ_RELEASE_ASSERT(context);
+
+    nsRefreshDriver* driver = context->RefreshDriver();
+    MOZ_RELEASE_ASSERT(driver);
+
+    mRequestedFrameRefreshObserver =
+      new RequestedFrameRefreshObserver(this, driver);
+  }
+
+  mRequestedFrameRefreshObserver->Register();
+}
+
+bool
+HTMLCanvasElement::IsFrameCaptureRequested() const
+{
+  for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
+    if (!listener) {
+      continue;
+    }
+
+    if (listener->FrameCaptureRequested()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
+{
+  RefPtr<SourceSurface> surface = aSurface;
+
+  CairoImage::Data imageData;
+  imageData.mSize = surface->GetSize();
+  imageData.mSourceSurface = surface;
+
+  nsRefPtr<CairoImage> image = new CairoImage();
+  image->SetData(imageData);
+
+  // Loop backwards to allow removing elements in the loop.
+  for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
+    WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i];
+    if (!listener) {
+      // listener was destroyed. Remove it from the list.
+      mRequestedFrameListeners.RemoveElementAt(i);
+      continue;
+    }
+
+    nsRefPtr<Image> imageRefCopy = image.get();
+    listener->NewFrame(imageRefCopy.forget());
+  }
+
+  if (mRequestedFrameListeners.IsEmpty()) {
+    mRequestedFrameRefreshObserver->Unregister();
+  }
+}
+
 already_AddRefed<SourceSurface>
 HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
 {
   if (!mCurrentContext)
     return nullptr;
 
   return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
 }
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -2,51 +2,92 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(mozilla_dom_HTMLCanvasElement_h)
 #define mozilla_dom_HTMLCanvasElement_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/WeakPtr.h"
 #include "nsIDOMHTMLCanvasElement.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsSize.h"
 #include "nsError.h"
 
 #include "mozilla/gfx/Rect.h"
 
 class nsICanvasRenderingContextInternal;
 class nsITimerCallback;
 
 namespace mozilla {
 
 namespace layers {
 class CanvasLayer;
+class Image;
 class LayerManager;
 } // namespace layers
 namespace gfx {
 class SourceSurface;
 } // namespace gfx
 
 namespace dom {
 class CanvasCaptureMediaStream;
 class File;
 class FileCallback;
 class HTMLCanvasPrintState;
 class PrintCallback;
+class RequestedFrameRefreshObserver;
 
 enum class CanvasContextType : uint8_t {
   NoContext,
   Canvas2D,
   WebGL1,
   WebGL2
 };
 
+/*
+ * FrameCaptureListener is used by captureStream() as a way of getting video
+ * frames from the canvas. On a refresh driver tick after something has been
+ * drawn to the canvas since the last such tick, all registered
+ * FrameCaptureListeners whose `mFrameCaptureRequested` equals `true`,
+ * will be given a copy of the just-painted canvas.
+ * All FrameCaptureListeners get the same copy.
+ */
+class FrameCaptureListener : public SupportsWeakPtr<FrameCaptureListener>
+{
+public:
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(FrameCaptureListener)
+
+  FrameCaptureListener()
+    : mFrameCaptureRequested(false) {}
+
+  /*
+   * Called when a frame capture is desired on next paint.
+   */
+  void RequestFrameCapture() { mFrameCaptureRequested = true; }
+
+  /*
+   * Indicates to the canvas whether or not this listener has requested a frame.
+   */
+  bool FrameCaptureRequested() const { return mFrameCaptureRequested; }
+
+  /*
+   * Interface through which new video frames will be provided while
+   * `mFrameCaptureRequested` is `true`.
+   */
+  virtual void NewFrame(already_AddRefed<layers::Image> aImage) = 0;
+
+protected:
+  virtual ~FrameCaptureListener() {}
+
+  bool mFrameCaptureRequested;
+};
+
 class HTMLCanvasElement final : public nsGenericHTMLElement,
                                 public nsIDOMHTMLCanvasElement
 {
   enum {
     DEFAULT_CANVAS_WIDTH = 300,
     DEFAULT_CANVAS_HEIGHT = 150
   };
 
@@ -166,16 +207,39 @@ public:
   /*
    * Returns true if the canvas context content is guaranteed to be opaque
    * across its entire area.
    */
   bool GetIsOpaque();
 
   virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
 
+  /*
+   * Register a FrameCaptureListener with this canvas.
+   * The canvas hooks into the RefreshDriver while there are
+   * FrameCaptureListeners registered.
+   * The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
+   * caller's responsibility to keep them alive. Once a registered
+   * FrameCaptureListener is destroyed it will be automatically deregistered.
+   */
+  void RegisterFrameCaptureListener(FrameCaptureListener* aListener);
+
+  /*
+   * Returns true when there is at least one registered FrameCaptureListener
+   * that has requested a frame capture.
+   */
+  bool IsFrameCaptureRequested() const;
+
+  /*
+   * Called by the RefreshDriver hook when a frame has been captured.
+   * Makes a copy of the provided surface and hands it to all
+   * FrameCaptureListeners having requested frame capture.
+   */
+  void SetFrameCapture(already_AddRefed<gfx::SourceSurface> aSurface);
+
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult) override;
   nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, int32_t aModType) const override;
 
   // SetAttr override.  C++ is stupid, so have to override both
   // overloaded methods.
@@ -209,16 +273,23 @@ public:
   bool ShouldForceInactiveLayer(LayerManager *aManager);
 
   // Call this whenever we need future changes to the canvas
   // to trigger fresh invalidation requests. This needs to be called
   // whenever we render the canvas contents to the screen, or whenever we
   // take a snapshot of the canvas that needs to be "live" (e.g. -moz-element).
   void MarkContextClean();
 
+  // Call this after capturing a frame, so we can avoid unnecessary surface
+  // copies for future frames when no drawing has occurred.
+  void MarkContextCleanForFrameCapture();
+
+  // Starts returning false when something is drawn.
+  bool IsContextCleanForFrameCapture();
+
   nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
 
 protected:
   virtual ~HTMLCanvasElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsIntSize GetWidthHeight();
@@ -241,16 +312,18 @@ protected:
                             nsISupports** aResult);
   void CallPrintCallback();
 
   CanvasContextType mCurrentContextType;
   nsRefPtr<HTMLCanvasElement> mOriginalCanvas;
   nsRefPtr<PrintCallback> mPrintCallback;
   nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
   nsRefPtr<HTMLCanvasPrintState> mPrintState;
+  nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
+  nsRefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
 
 public:
   // Record whether this canvas should be write-only or not.
   // We set this when script paints an image from a different origin.
   // We also transitively set it when script paints a canvas which
   // is itself write-only.
   bool                     mWriteOnly;
 
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -213,40 +213,44 @@ HTMLLabelElement::Reset()
 }
 
 NS_IMETHODIMP
 HTMLLabelElement::SubmitNamesValues(nsFormSubmission* aFormSubmission)
 {
   return NS_OK;
 }
 
-void
+bool
 HTMLLabelElement::PerformAccesskey(bool aKeyCausesActivation,
                                    bool aIsTrustedEvent)
 {
   if (!aKeyCausesActivation) {
     nsRefPtr<Element> element = GetLabeledElement();
-    if (element)
-      element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
+    if (element) {
+      return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
+    }
   } else {
     nsPresContext *presContext = GetPresContext(eForUncomposedDoc);
-    if (!presContext)
-      return;
+    if (!presContext) {
+      return false;
+    }
 
     // Click on it if the users prefs indicate to do so.
     WidgetMouseEvent event(aIsTrustedEvent, eMouseClick,
                            nullptr, WidgetMouseEvent::eReal);
     event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
 
     nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
                                             openAllowed : openAbused);
 
     EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
                               &event);
   }
+
+  return aKeyCausesActivation;
 }
 
 nsGenericHTMLElement*
 HTMLLabelElement::GetLabeledElement() const
 {
   nsAutoString elementId;
 
   if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_for, elementId)) {
--- a/dom/html/HTMLLabelElement.h
+++ b/dom/html/HTMLLabelElement.h
@@ -64,17 +64,17 @@ public:
   NS_IMETHOD Reset() override;
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission) override;
 
   virtual bool IsDisabled() const override { return false; }
 
   // nsIContent
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) override;
-  virtual void PerformAccesskey(bool aKeyCausesActivation,
+  virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   nsGenericHTMLElement* GetLabeledElement() const;
 protected:
   virtual ~HTMLLabelElement();
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/html/HTMLLegendElement.cpp
+++ b/dom/html/HTMLLegendElement.cpp
@@ -123,23 +123,24 @@ HTMLLegendElement::Focus(ErrorResult& aE
   }
 
   nsCOMPtr<nsIDOMElement> result;
   aError = fm->MoveFocus(nullptr, this, nsIFocusManager::MOVEFOCUS_FORWARD,
                          nsIFocusManager::FLAG_NOPARENTFRAME,
                          getter_AddRefs(result));
 }
 
-void
+bool
 HTMLLegendElement::PerformAccesskey(bool aKeyCausesActivation,
                                     bool aIsTrustedEvent)
 {
   // just use the same behaviour as the focus method
   ErrorResult rv;
   Focus(rv);
+  return NS_SUCCEEDED(rv.StealNSResult());
 }
 
 already_AddRefed<HTMLFormElement>
 HTMLLegendElement::GetForm()
 {
   Element* form = GetFormElement();
   MOZ_ASSERT_IF(form, form->IsHTMLElement(nsGkAtoms::form));
   nsRefPtr<HTMLFormElement> ret = static_cast<HTMLFormElement*>(form);
--- a/dom/html/HTMLLegendElement.h
+++ b/dom/html/HTMLLegendElement.h
@@ -22,17 +22,17 @@ public:
   {
   }
 
   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLLegendElement, legend)
 
   using nsGenericHTMLElement::Focus;
   virtual void Focus(ErrorResult& aError) override;
 
-  virtual void PerformAccesskey(bool aKeyCausesActivation,
+  virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2218,16 +2218,23 @@ HTMLMediaElement::Play(ErrorResult& aRv)
 {
   // Prevent media element from being auto-started by a script when
   // media.autoplay.enabled=false
   if (!mHasUserInteraction
       && !IsAutoplayEnabled()
       && !EventStateManager::IsHandlingUserInput()
       && !nsContentUtils::IsCallerChrome()) {
     LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
+#if defined(MOZ_WIDGET_ANDROID)
+    nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+                                         static_cast<nsIContent*>(this),
+                                         NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
+                                         false,
+                                         false);
+#endif
     return;
   }
 
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2734,42 +2734,50 @@ nsGenericHTMLElement::RegUnRegAccessKey(
     if (aDoReg) {
       esm->RegisterAccessKey(this, (uint32_t)accessKey.First());
     } else {
       esm->UnregisterAccessKey(this, (uint32_t)accessKey.First());
     }
   }
 }
 
-void
+bool
 nsGenericHTMLElement::PerformAccesskey(bool aKeyCausesActivation,
                                        bool aIsTrustedEvent)
 {
   nsPresContext* presContext = GetPresContext(eForUncomposedDoc);
-  if (!presContext)
-    return;
+  if (!presContext) {
+    return false;
+  }
 
   // It's hard to say what HTML4 wants us to do in all cases.
-  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+  bool focused = true;
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
     fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY);
+
+    // Return true if the element became the current focus within its window.
+    nsPIDOMWindow* window = OwnerDoc()->GetWindow();
+    focused = (window && window->GetFocusedNode());
   }
 
   if (aKeyCausesActivation) {
     // Click on it if the users prefs indicate to do so.
     WidgetMouseEvent event(aIsTrustedEvent, eMouseClick, nullptr,
                            WidgetMouseEvent::eReal);
     event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
 
     nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
                                             openAllowed : openAbused);
 
     EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
                               presContext, &event);
   }
+
+  return focused;
 }
 
 const nsAttrName*
 nsGenericHTMLElement::InternalGetExistingAttrNameFromQName(const nsAString& aStr) const
 {
   if (IsInHTMLDocument()) {
     nsAutoString lower;
     nsContentUtils::ASCIIToLower(aStr, lower);
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -592,17 +592,17 @@ public:
   }
   /**
    * Returns true if a subclass is not allowed to override the value returned
    * in aIsFocusable.
    */
   virtual bool IsHTMLFocusable(bool aWithMouse,
                                bool *aIsFocusable,
                                int32_t *aTabIndex);
-  virtual void PerformAccesskey(bool aKeyCausesActivation,
+  virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
 
   /**
    * Check if an event for an anchor can be handled
    * @return true if the event can be handled, false otherwise
    */
   bool CheckHandleEventForAnchorsPreconditions(
          mozilla::EventChainVisitor& aVisitor);
--- a/dom/interfaces/apps/nsIAppsService.idl
+++ b/dom/interfaces/apps/nsIAppsService.idl
@@ -11,17 +11,17 @@ interface nsIURI;
 #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } }
 #define APPS_SERVICE_CONTRACTID "@mozilla.org/AppsService;1"
 %}
 
 /*
  * This service allows accessing some DOMApplicationRegistry methods from
  * non-javascript code.
  */
-[scriptable, uuid(8a035714-ed14-446f-8bd7-837e91cdce9e)]
+[scriptable, uuid(711cfab6-7b72-4aa2-a60c-17952ea05661)]
 interface nsIAppsService : nsISupports
 {
   mozIApplication getAppByManifestURL(in DOMString manifestURL);
 
   /**
    * Returns a Promise for the manifest for a given manifestURL.
    * This is only supported in the parent process: the promise will be rejected
    * in content processes.
@@ -84,10 +84,15 @@ interface nsIAppsService : nsISupports
    */
   DOMString getScopeByLocalId(in unsigned long localId);
 
   /**
    * Returns true if this uri is a script or css resource loaded
    * from an extension.
    * Available only in the parent process.
    */
-   bool isExtensionResource(in nsIURI uri);
+  bool isExtensionResource(in nsIURI uri);
+
+  /**
+   * Reads the manifest file for this app and update the DataStore map
+   */
+  void updateDataStoreEntriesFromLocalId(in unsigned long localId);
 };
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -15,17 +15,17 @@ interface nsIURI;
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
 typedef unsigned short CSPDirective;
 
-[scriptable, uuid(b622b0f8-ee51-4f7a-8c23-b3bce20e752e)]
+[scriptable, uuid(fe07ab08-21ba-470c-8b89-78d0e7298c68)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
   /**
    * Directives supported by Content Security Policy.  These are enums for
    * the CSPDirective type.
    * The NO_DIRECTIVE entry is  used for checking default permissions and
    * returning failure when asking CSP which directive to check.
    *
@@ -96,97 +96,48 @@ interface nsIContentSecurityPolicy : nsI
    * @param aPolicy
    *        String representation of the policy (e.g., header value)
    * @param reportOnly
    *        Should this policy affect content, script and style processing or
    *        just send reports if it is violated?
    */
   void appendPolicy(in AString policyString, in boolean reportOnly);
 
-  /**
-   * Whether this policy allows in-page script.
-   * @param shouldReportViolations
-   *     Whether or not the use of inline script should be reported.
-   *     This function always returns "true" for report-only policies, but when
-   *     any policy (report-only or otherwise) is violated,
-   *     shouldReportViolations is true as well.
+  /*
+   * Whether this policy allows inline script or style.
+   * @param aContentPolicyType Either TYPE_SCRIPT or TYPE_STYLESHEET
+   * @param aNonce The nonce string to check against the policy
+   * @param aContent  The content of the inline resource to hash
+   *        (and compare to the hashes listed in the policy)
+   * @param aLineNumber The line number of the inline resource
+   *        (used for reporting)
    * @return
-   *     Whether or not the effects of the inline script should be allowed
-   *     (block the compilation if false).
+   *     Whether or not the effects of the inline style should be allowed
+   *     (block the rules if false).
    */
-  boolean getAllowsInlineScript(out boolean shouldReportViolations);
+  boolean getAllowsInline(in nsContentPolicyType aContentPolicyType,
+                          in AString aNonce,
+                          in AString aContent,
+                          in unsigned long aLineNumber);
 
   /**
    * whether this policy allows eval and eval-like functions
    * such as setTimeout("code string", time).
    * @param shouldReportViolations
    *     Whether or not the use of eval should be reported.
    *     This function returns "true" when violating report-only policies, but
    *     when any policy (report-only or otherwise) is violated,
    *     shouldReportViolations is true as well.
    * @return
    *     Whether or not the effects of the eval call should be allowed
    *     (block the call if false).
    */
   boolean getAllowsEval(out boolean shouldReportViolations);
 
   /**
-   * Whether this policy allows in-page styles.
-   * This includes <style> tags with text content and style="" attributes in
-   * HTML elements.
-   * @param shouldReportViolations
-   *     Whether or not the use of inline style should be reported.
-   *     If there are report-only policies, this function may return true
-   *     (don't block), but one or more policy may still want to send
-   *     violation reports so shouldReportViolations will be true even if the
-   *     inline style should be permitted.
-   * @return
-   *     Whether or not the effects of the inline style should be allowed
-   *     (block the rules if false).
-   */
-  boolean getAllowsInlineStyle(out boolean shouldReportViolations);
-
-  /**
-   * Whether this policy accepts the given nonce
-   * @param aNonce
-   *     The nonce string to check against the policy
-   * @param aContentType
-   *     The type of element on which we encountered this nonce
-   * @param shouldReportViolation
-   *     Whether or not the use of an incorrect nonce should be reported.
-   *     This function always returns "true" for report-only policies, but when
-   *     the report-only policy is violated, shouldReportViolation is true as
-   *     well.
-   * @return
-   *     Whether or not this nonce is valid
-   */
-   boolean getAllowsNonce(in AString aNonce,
-                          in unsigned long aContentType,
-                          out boolean shouldReportViolation);
-
-   /**
-    * Whether this policy accepts the given inline resource based on the hash
-    * of its content.
-    * @param aContent
-    *     The content of the inline resource to hash (and compare to the
-    *     hashes listed in the policy)
-    * @param aContentType
-    *     The type of inline element (script or style)
-    * @param shouldReportViolation
-    *     Whether this inline resource should be reported as a hash-source
-    *     violation. If there are no hash-sources in the policy, this is
-    *     always false.
-    * @return
-    *     Whether or not this inline resource is whitelisted by a hash-source
-    */
-   boolean getAllowsHash(in AString aContent,
-                         in unsigned short aContentType,
-                         out boolean shouldReportViolation);
-
-  /**
    * For each violated policy (of type violationType), log policy violation on
    * the Error Console and send a report to report-uris present in the violated
    * policies.
    *
    * @param violationType
    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
    * @param sourceFile
    *     name of the source file containing the violation (if available)
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -773,16 +773,26 @@ child:
     AppOfflineStatus(uint32_t id, bool offline);
 
     /**
      * Tell the browser that its frame loader has been swapped
      * with another.
      */
     SwappedWithOtherRemoteLoader();
 
+    /**
+     * A potential accesskey was just pressed. Look for accesskey targets
+     * using the list of provided charCodes.
+     *
+     * @param charCode array of potential character codes
+     * @param isTrusted true if triggered by a trusted key event
+     * @param modifierMask indicates which accesskey modifiers are pressed
+     */
+    HandleAccessKey(uint32_t[] charCodes, bool isTrusted, int32_t modifierMask);
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -60,16 +60,17 @@
 #include "nsIDocumentInlines.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsFocusManager.h"
+#include "EventStateManager.h"
 #include "nsIDocShell.h"
 #include "nsIFrame.h"
 #include "nsIURI.h"
 #include "nsIURIFixup.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIWebBrowser.h"
 #include "nsIWebBrowserFocus.h"
 #include "nsIWebBrowserSetup.h"
@@ -2493,16 +2494,33 @@ TabChild::RecvSwappedWithOtherRemoteLoad
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, true);
 
   docShell->SetInFrameSwap(false);
 
   return true;
 }
 
 bool
+TabChild::RecvHandleAccessKey(nsTArray<uint32_t>&& aCharCodes,
+                              const bool& aIsTrusted,
+                              const int32_t& aModifierMask)
+{
+  nsCOMPtr<nsIDocument> document(GetDocument());
+  nsCOMPtr<nsIPresShell> presShell = document->GetShell();
+  if (presShell) {
+    nsPresContext* pc = presShell->GetPresContext();
+    if (pc) {
+      pc->EventStateManager()->HandleAccessKey(pc, aCharCodes, aIsTrusted, aModifierMask);
+    }
+  }
+
+  return true;
+}
+
+bool
 TabChild::RecvDestroy()
 {
   MOZ_ASSERT(mDestroyed == false);
   mDestroyed = true;
 
   while (mActiveSuppressDisplayport > 0) {
     APZCCallbackHelper::SuppressDisplayport(false);
     mActiveSuppressDisplayport--;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -475,16 +475,20 @@ public:
       nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav);
       return GetFrom(docShell);
     }
 
     virtual bool RecvUIResolutionChanged(const float& aDpi, const double& aScale) override;
 
     virtual bool RecvThemeChanged(nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache) override;
 
+    virtual bool RecvHandleAccessKey(nsTArray<uint32_t>&& aCharCodes,
+                                     const bool& aIsTrusted,
+                                     const int32_t& aModifierMask) override;
+
     /**
      * Native widget remoting protocol for use with windowed plugins with e10s.
      */
     PPluginWidgetChild* AllocPPluginWidgetChild() override;
     bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor) override;
     nsresult CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut);
 
     LayoutDeviceIntPoint GetChromeDisplacement() { return mChromeDisp; };
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1157,16 +1157,26 @@ TabParent::ThemeChanged()
     // LookAndFeel should have the up-to-date values, which we now
     // send down to the child. We do this for every remote tab for now,
     // but bug 1156934 has been filed to do it once per content process.
     unused << SendThemeChanged(LookAndFeel::GetIntCache());
   }
 }
 
 void
+TabParent::HandleAccessKey(nsTArray<uint32_t>& aCharCodes,
+                           const bool& aIsTrusted,
+                           const int32_t& aModifierMask)
+{
+  if (!mIsDestroyed) {
+    unused << SendHandleAccessKey(aCharCodes, aIsTrusted, aModifierMask);
+  }
+}
+
+void
 TabParent::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
                             const mozilla::CSSPoint& aDestination)
 {
   if (!mIsDestroyed) {
     unused << SendRequestFlingSnap(aScrollId, aDestination);
   }
 }
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -262,16 +262,19 @@ public:
     // XXX/cjones: it's not clear what we gain by hiding these
     // message-sending functions under a layer of indirection and
     // eating the return values
     void Show(const ScreenIntSize& size, bool aParentIsActive);
     void UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size);
     void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
     void UIResolutionChanged();
     void ThemeChanged();
+    void HandleAccessKey(nsTArray<uint32_t>& aCharCodes,
+                         const bool& aIsTrusted,
+                         const int32_t& aModifierMask);
     void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
                           const mozilla::CSSPoint& aDestination);
     void AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration);
     void HandleDoubleTap(const CSSPoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid);
     void HandleSingleTap(const CSSPoint& aPoint,
                          Modifiers aModifiers,
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -124,16 +124,17 @@ if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_
 LOCAL_INCLUDES += [
     '/caps',
     '/chrome',
     '/docshell/base',
     '/dom/base',
     '/dom/bluetooth/common',
     '/dom/bluetooth/ipc',
     '/dom/devicestorage',
+    '/dom/events',
     '/dom/filesystem',
     '/dom/fmradio/ipc',
     '/dom/geolocation',
     '/dom/media/webspeech/synth/ipc',
     '/dom/mobilemessage/ipc',
     '/dom/security',
     '/dom/storage',
     '/dom/workers',
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -173,37 +173,25 @@ nsresult nsJSThunk::EvaluateScript(nsICh
     nsresult rv;
 
     // CSP check: javascript: URIs disabled unless "inline" scripts are
     // allowed.
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     rv = principal->GetCsp(getter_AddRefs(csp));
     NS_ENSURE_SUCCESS(rv, rv);
     if (csp) {
-        bool allowsInline = true;
-        bool reportViolations = false;
-        rv = csp->GetAllowsInlineScript(&reportViolations, &allowsInline);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (reportViolations) {
-            // gather information to log with violation report
-            nsCOMPtr<nsIURI> uri;
-            principal->GetURI(getter_AddRefs(uri));
-            nsAutoCString asciiSpec;
-            uri->GetAsciiSpec(asciiSpec);
-            csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
-                                     NS_ConvertUTF8toUTF16(asciiSpec),
-                                     NS_ConvertUTF8toUTF16(mURL),
-                                     0,
-                                     EmptyString(),
-                                     EmptyString());
-        }
+        bool allowsInlineScript = true;
+        rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
+                                  EmptyString(), // aNonce
+                                  EmptyString(), // aContent
+                                  0,             // aLineNumber
+                                  &allowsInlineScript);
 
         //return early if inline scripts are not allowed
-        if (!allowsInline) {
+        if (!allowsInlineScript) {
           return NS_ERROR_DOM_RETVAL_UNDEFINED;
         }
     }
 
     // Get the global object we should be running on.
     nsIScriptGlobalObject* global = GetGlobalObject(aChannel);
     if (!global) {
         return NS_ERROR_FAILURE;
--- a/dom/media/CanvasCaptureMediaStream.cpp
+++ b/dom/media/CanvasCaptureMediaStream.cpp
@@ -4,301 +4,202 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CanvasCaptureMediaStream.h"
 #include "DOMMediaStream.h"
 #include "gfxPlatform.h"
 #include "ImageContainer.h"
 #include "MediaStreamGraph.h"
 #include "mozilla/dom/CanvasCaptureMediaStreamBinding.h"
-#include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/gfx/2D.h"
-#include "mozilla/Mutex.h"
+#include "mozilla/Atomics.h"
 #include "nsContentUtils.h"
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace dom {
 
 class OutputStreamDriver::StreamListener : public MediaStreamListener
 {
 public:
   explicit StreamListener(OutputStreamDriver* aDriver,
+                          TrackID aTrackId,
                           SourceMediaStream* aSourceStream)
-    : mSourceStream(aSourceStream)
-    , mMutex("CanvasCaptureMediaStream::OSD::StreamListener")
-    , mDriver(aDriver)
+    : mEnded(false)
+    , mSourceStream(aSourceStream)
+    , mTrackId(aTrackId)
+    , mMutex("CanvasCaptureMediaStream OutputStreamDriver::StreamListener")
+    , mImage(nullptr)
   {
-    MOZ_ASSERT(mDriver);
     MOZ_ASSERT(mSourceStream);
   }
 
-  void Forget() {
-    MOZ_ASSERT(NS_IsMainThread());
+  void EndStream() {
+    mEnded = true;
+  }
 
+  void SetImage(const nsRefPtr<layers::Image>& aImage)
+  {
     MutexAutoLock lock(mMutex);
-    mDriver = nullptr;
+    mImage = aImage;
   }
 
   virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
   {
     // Called on the MediaStreamGraph thread.
+    StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
+    if (delta > 0) {
+      MutexAutoLock lock(mMutex);
+      MOZ_ASSERT(mSourceStream);
 
-    MutexAutoLock lock(mMutex);
-    if (mDriver) {
-      mDriver->NotifyPull(aDesiredTime);
-    } else {
-      // The DOM stream is dead, let's end it
+      nsRefPtr<Image> image = mImage;
+      IntSize size = image ? image->GetSize() : IntSize(0, 0);
+      VideoSegment segment;
+      segment.AppendFrame(image.forget(), delta, size);
+
+      mSourceStream->AppendToTrack(mTrackId, &segment);
+    }
+
+    if (mEnded) {
       mSourceStream->EndAllTrackAndFinish();
     }
   }
 
 protected:
   ~StreamListener() { }
 
 private:
-  nsRefPtr<SourceMediaStream> mSourceStream;
+  Atomic<bool> mEnded;
+  const nsRefPtr<SourceMediaStream> mSourceStream;
+  const TrackID mTrackId;
 
-  // The below members are protected by mMutex.
   Mutex mMutex;
-  // This is a raw pointer to avoid a reference cycle with OutputStreamDriver.
-  // Accessed on main and MediaStreamGraph threads, set on main thread.
-  OutputStreamDriver* mDriver;
+  // The below members are protected by mMutex.
+  nsRefPtr<layers::Image> mImage;
 };
 
-OutputStreamDriver::OutputStreamDriver(CanvasCaptureMediaStream* aDOMStream,
+OutputStreamDriver::OutputStreamDriver(SourceMediaStream* aSourceStream,
                                        const TrackID& aTrackId)
-  : mDOMStream(aDOMStream)
-  , mSourceStream(nullptr)
-  , mStarted(false)
-  , mStreamListener(nullptr)
-  , mTrackId(aTrackId)
-  , mMutex("CanvasCaptureMediaStream::OutputStreamDriver")
-  , mImage(nullptr)
+  : FrameCaptureListener()
+  , mSourceStream(aSourceStream)
+  , mStreamListener(new StreamListener(this, aTrackId, aSourceStream))
 {
-  MOZ_ASSERT(mDOMStream);
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mSourceStream);
+  mSourceStream->AddListener(mStreamListener);
+  mSourceStream->AddTrack(aTrackId, 0, new VideoSegment());
+  mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+  mSourceStream->SetPullEnabled(true);
+
+  // All CanvasCaptureMediaStreams shall at least get one frame.
+  mFrameCaptureRequested = true;
 }
 
 OutputStreamDriver::~OutputStreamDriver()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   if (mStreamListener) {
     // MediaStreamGraph will keep the listener alive until it can finish the
     // stream on the next NotifyPull().
-    mStreamListener->Forget();
-  }
-}
-
-nsresult
-OutputStreamDriver::Start()
-{
-  if (mStarted) {
-    return NS_ERROR_ALREADY_INITIALIZED;
-  }
-
-  MOZ_ASSERT(mDOMStream);
-
-  mDOMStream->CreateDOMTrack(mTrackId, MediaSegment::VIDEO);
-
-  mSourceStream = mDOMStream->GetStream()->AsSourceStream();
-  MOZ_ASSERT(mSourceStream);
-
-  mStreamListener = new StreamListener(this, mSourceStream);
-  mSourceStream->AddListener(mStreamListener);
-  mSourceStream->AddTrack(mTrackId, 0, new VideoSegment());
-  mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-  mSourceStream->SetPullEnabled(true);
-
-  // Run StartInternal() in stable state to allow it to directly capture a frame
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewRunnableMethod(this, &OutputStreamDriver::StartInternal);
-  nsContentUtils::RunInStableState(runnable.forget());
-
-  mStarted = true;
-  return NS_OK;
-}
-
-void
-OutputStreamDriver::ForgetDOMStream()
-{
-  if (mStreamListener) {
-    mStreamListener->Forget();
-  }
-  mDOMStream = nullptr;
-}
-
-void
-OutputStreamDriver::AppendToTrack(StreamTime aDuration)
-{
-  MOZ_ASSERT(mSourceStream);
-
-  MutexAutoLock lock(mMutex);
-
-  nsRefPtr<Image> image = mImage;
-  IntSize size = image ? image->GetSize() : IntSize(0, 0);
-  VideoSegment segment;
-  segment.AppendFrame(image.forget(), aDuration, size);
-
-  mSourceStream->AppendToTrack(mTrackId, &segment);
-}
-
-void
-OutputStreamDriver::NotifyPull(StreamTime aDesiredTime)
-{
-  StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
-  if (delta > 0) {
-    // nullptr images are allowed
-    AppendToTrack(delta);
+    mStreamListener->EndStream();
   }
 }
 
 void
-OutputStreamDriver::SetImage(Image* aImage)
+OutputStreamDriver::SetImage(const nsRefPtr<layers::Image>& aImage)
 {
-  MutexAutoLock lock(mMutex);
-  mImage = aImage;
+  if (mStreamListener) {
+    mStreamListener->SetImage(aImage);
+  }
 }
 
 // ----------------------------------------------------------------------
 
 class TimerDriver : public OutputStreamDriver
-                  , public nsITimerCallback
 {
 public:
-  explicit TimerDriver(CanvasCaptureMediaStream* aDOMStream,
+  explicit TimerDriver(SourceMediaStream* aSourceStream,
                        const double& aFPS,
                        const TrackID& aTrackId)
-    : OutputStreamDriver(aDOMStream, aTrackId)
+    : OutputStreamDriver(aSourceStream, aTrackId)
     , mFPS(aFPS)
     , mTimer(nullptr)
   {
-  }
-
-  void ForgetDOMStream() override
-  {
-    if (mTimer) {
-      mTimer->Cancel();
-      mTimer = nullptr;
-    }
-    OutputStreamDriver::ForgetDOMStream();
-  }
-
-  nsresult
-  TakeSnapshot()
-  {
-    // mDOMStream can't be killed while we're on main thread
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(DOMStream());
-
-    if (!DOMStream()->Canvas()) {
-      // DOMStream's canvas pointer was garbage collected. We can abort now.
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-    MOZ_ASSERT(DOMStream()->Canvas());
-
-    if (DOMStream()->Canvas()->IsWriteOnly()) {
-      return NS_ERROR_DOM_SECURITY_ERR;
-    }
-
-    // Pass `nullptr` to force alpha-premult.
-    RefPtr<SourceSurface> snapshot = DOMStream()->Canvas()->GetSurfaceSnapshot(nullptr);
-    if (!snapshot) {
-      return NS_ERROR_FAILURE;
-    }
-
-    RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
-    if (!data) {
-      return NS_ERROR_FAILURE;
-    }
-
-    RefPtr<DataSourceSurface> copy;
-
-    {
-      DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
-      if (!read.IsMapped()) {
-        return NS_ERROR_FAILURE;
-      }
-
-      copy = Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
-                                                        data->GetFormat(),
-                                                        read.GetStride());
-      if (!copy) {
-        return NS_ERROR_FAILURE;
-      }
-
-      DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
-      if (!write.IsMapped()) {
-        return NS_ERROR_FAILURE;
-      }
-
-      MOZ_ASSERT(read.GetStride() == write.GetStride());
-      MOZ_ASSERT(data->GetSize() == copy->GetSize());
-      MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
-
-      memcpy(write.GetData(), read.GetData(),
-             write.GetStride() * copy->GetSize().height);
-    }
-
-    CairoImage::Data imageData;
-    imageData.mSize = copy->GetSize();
-    imageData.mSourceSurface = copy;
-
-    RefPtr<CairoImage> image = new layers::CairoImage();
-    image->SetData(imageData);
-
-    SetImage(image);
-    return NS_OK;
-  }
-
-  NS_IMETHODIMP
-  Notify(nsITimer* aTimer) override
-  {
-    nsresult rv = TakeSnapshot();
-    if (NS_FAILED(rv)) {
-      aTimer->Cancel();
-    }
-    return rv;
-  }
-
-  virtual void RequestFrame() override
-  {
-    TakeSnapshot();
-  }
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-protected:
-  virtual ~TimerDriver() {}
-
-  virtual void StartInternal() override
-  {
-    // Always capture at least one frame.
-    DebugOnly<nsresult> rv = TakeSnapshot();
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-
     if (mFPS == 0.0) {
       return;
     }
 
     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
     if (!mTimer) {
       return;
     }
-    mTimer->InitWithCallback(this, int(1000 / mFPS), nsITimer::TYPE_REPEATING_SLACK);
+    mTimer->InitWithFuncCallback(&TimerTick, this, int(1000 / mFPS), nsITimer::TYPE_REPEATING_SLACK);
+  }
+
+  static void TimerTick(nsITimer* aTimer, void* aClosure)
+  {
+    MOZ_ASSERT(aClosure);
+    TimerDriver* driver = static_cast<TimerDriver*>(aClosure);
+
+    driver->RequestFrameCapture();
   }
 
+  void NewFrame(already_AddRefed<Image> aImage) override
+  {
+    nsRefPtr<Image> image = aImage;
+
+    if (!mFrameCaptureRequested) {
+      return;
+    }
+
+    mFrameCaptureRequested = false;
+    SetImage(image.forget());
+  }
+
+  void Forget() override
+  {
+    if (mTimer) {
+      mTimer->Cancel();
+      mTimer = nullptr;
+    }
+  }
+
+protected:
+  virtual ~TimerDriver() {}
+
 private:
   const double mFPS;
   nsCOMPtr<nsITimer> mTimer;
 };
 
-NS_IMPL_ADDREF_INHERITED(TimerDriver, OutputStreamDriver)
-NS_IMPL_RELEASE_INHERITED(TimerDriver, OutputStreamDriver)
-NS_IMPL_QUERY_INTERFACE(TimerDriver, nsITimerCallback)
+// ----------------------------------------------------------------------
+
+class AutoDriver : public OutputStreamDriver
+{
+public:
+  explicit AutoDriver(SourceMediaStream* aSourceStream,
+                      const TrackID& aTrackId)
+    : OutputStreamDriver(aSourceStream, aTrackId) {}
+
+  void NewFrame(already_AddRefed<Image> aImage) override
+  {
+    // Don't reset `mFrameCaptureRequested` since AutoDriver shall always have
+    // `mFrameCaptureRequested` set to true.
+    // This also means we should accept every frame as NewFrame is called only
+    // after something changed.
+
+    nsRefPtr<Image> image = aImage;
+    SetImage(image.forget());
+  }
+
+protected:
+  virtual ~AutoDriver() {}
+};
 
 // ----------------------------------------------------------------------
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream, DOMMediaStream,
                                    mCanvas)
 
 NS_IMPL_ADDREF_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
@@ -310,59 +211,66 @@ CanvasCaptureMediaStream::CanvasCaptureM
   : mCanvas(aCanvas)
   , mOutputStreamDriver(nullptr)
 {
 }
 
 CanvasCaptureMediaStream::~CanvasCaptureMediaStream()
 {
   if (mOutputStreamDriver) {
-    mOutputStreamDriver->ForgetDOMStream();
+    mOutputStreamDriver->Forget();
   }
 }
 
 JSObject*
 CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::CanvasCaptureMediaStreamBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 CanvasCaptureMediaStream::RequestFrame()
 {
+  MOZ_ASSERT(mOutputStreamDriver);
   if (mOutputStreamDriver) {
-    mOutputStreamDriver->RequestFrame();
+    mOutputStreamDriver->RequestFrameCapture();
   }
 }
 
 nsresult
 CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
                                const TrackID& aTrackId)
 {
   if (!aFPS.WasPassed()) {
-    // TODO (Bug 1152298): Implement a real AutoDriver.
-    // We use a 30FPS TimerDriver for now.
-    mOutputStreamDriver = new TimerDriver(this, 30.0, aTrackId);
+    mOutputStreamDriver =
+      new AutoDriver(GetStream()->AsSourceStream(), aTrackId);
   } else if (aFPS.Value() < 0) {
     return NS_ERROR_ILLEGAL_VALUE;
   } else {
     // Cap frame rate to 60 FPS for sanity
     double fps = std::min(60.0, aFPS.Value());
-    mOutputStreamDriver = new TimerDriver(this, fps, aTrackId);
+    mOutputStreamDriver =
+      new TimerDriver(GetStream()->AsSourceStream(), fps, aTrackId);
   }
-  return mOutputStreamDriver->Start();
+  return NS_OK;
 }
 
 already_AddRefed<CanvasCaptureMediaStream>
 CanvasCaptureMediaStream::CreateSourceStream(nsIDOMWindow* aWindow,
                                              HTMLCanvasElement* aCanvas)
 {
   nsRefPtr<CanvasCaptureMediaStream> stream = new CanvasCaptureMediaStream(aCanvas);
   MediaStreamGraph* graph =
     MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
                                   AudioChannel::Normal);
   stream->InitSourceStream(aWindow, graph);
   return stream.forget();
 }
 
+FrameCaptureListener*
+CanvasCaptureMediaStream::FrameCaptureListener()
+{
+  return mOutputStreamDriver;
+}
+
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/media/CanvasCaptureMediaStream.h
+++ b/dom/media/CanvasCaptureMediaStream.h
@@ -2,98 +2,118 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_CanvasCaptureMediaStream_h_
 #define mozilla_dom_CanvasCaptureMediaStream_h_
 
 #include "DOMMediaStream.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
 #include "StreamBuffer.h"
 
 namespace mozilla {
 class DOMMediaStream;
 class MediaStreamListener;
 class SourceMediaStream;
 
 namespace layers {
 class Image;
 } // namespace layers
 
 namespace dom {
 class CanvasCaptureMediaStream;
 class HTMLCanvasElement;
+class OutputStreamFrameListener;
 
-class OutputStreamDriver
+/*
+ * The CanvasCaptureMediaStream is a MediaStream subclass that provides a video
+ * track containing frames from a canvas. See an architectural overview below.
+ *
+ * ----------------------------------------------------------------------------
+ *     === Main Thread ===              __________________________
+ *                                     |                          |
+ *                                     | CanvasCaptureMediaStream |
+ *                                     |__________________________|
+ *                                                  |
+ *                                                  | RequestFrame()
+ *                                                  v
+ *                                       ________________________
+ *  ________   FrameCaptureRequested?   |                        |
+ * |        | ------------------------> |   OutputStreamDriver   |
+ * | Canvas |  SetFrameCapture()        | (FrameCaptureListener) |
+ * |________| ------------------------> |________________________|
+ *                                                  |
+ *                                                  | SetImage()
+ *                                                  v
+ *                                         ___________________
+ *                                        |   StreamListener  |
+ * ---------------------------------------| (All image access |----------------
+ *     === MediaStreamGraph Thread ===    |   Mutex Guarded)  |
+ *                                        |___________________|
+ *                                              ^       |
+ *                                 NotifyPull() |       | AppendToTrack()
+ *                                              |       v
+ *                                      ___________________________
+ *                                     |                           |
+ *                                     |  MSG / SourceMediaStream  |
+ *                                     |___________________________|
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * Base class for drivers of the output stream.
+ * It is up to each sub class to implement the NewFrame() callback of
+ * FrameCaptureListener.
+ */
+class OutputStreamDriver : public FrameCaptureListener
 {
 public:
-  OutputStreamDriver(CanvasCaptureMediaStream* aDOMStream,
+  OutputStreamDriver(SourceMediaStream* aSourceStream,
                      const TrackID& aTrackId);
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OutputStreamDriver);
 
-  nsresult Start();
-
-  virtual void ForgetDOMStream();
+  /*
+   * Sub classes can SetImage() to update the image being appended to the
+   * output stream. It will be appended on the next NotifyPull from MSG.
+   */
+  void SetImage(const nsRefPtr<layers::Image>& aImage);
 
-  virtual void RequestFrame() { }
-
-  CanvasCaptureMediaStream* DOMStream() const { return mDOMStream; }
+  /*
+   * Makes sure any internal resources this driver is holding that may create
+   * reference cycles are released.
+   */
+  virtual void Forget() {}
 
 protected:
   virtual ~OutputStreamDriver();
   class StreamListener;
 
-  /*
-   * Appends mImage to video track for the desired duration.
-   */
-  void AppendToTrack(StreamTime aDuration);
-  void NotifyPull(StreamTime aDesiredTime);
-
-  /*
-   * Sub classes can SetImage() to update the image being appended to the
-   * output stream. It will be appended on the next NotifyPull from MSG.
-   */
-  void SetImage(layers::Image* aImage);
-
-  /*
-   * Called in main thread stable state to initialize sub classes.
-   */
-  virtual void StartInternal() = 0;
-
 private:
-  // This is a raw pointer to avoid a reference cycle between OutputStreamDriver
-  // and CanvasCaptureMediaStream. ForgetDOMStream() will be called by
-  // ~CanvasCaptureMediaStream() to make sure we don't do anything illegal.
-  CanvasCaptureMediaStream* mDOMStream;
   nsRefPtr<SourceMediaStream> mSourceStream;
-  bool mStarted;
   nsRefPtr<StreamListener> mStreamListener;
-  const TrackID mTrackId;
-
-  // The below members are protected by mMutex.
-  Mutex mMutex;
-  nsRefPtr<layers::Image> mImage;
 };
 
-class CanvasCaptureMediaStream: public DOMMediaStream
+class CanvasCaptureMediaStream : public DOMMediaStream
 {
 public:
   explicit CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
 
   nsresult Init(const dom::Optional<double>& aFPS, const TrackID& aTrackId);
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL
   HTMLCanvasElement* Canvas() const { return mCanvas; }
   void RequestFrame();
+  dom::FrameCaptureListener* FrameCaptureListener();
 
   /**
    * Create a CanvasCaptureMediaStream whose underlying stream is a SourceMediaStream.
    */
   static already_AddRefed<CanvasCaptureMediaStream>
   CreateSourceStream(nsIDOMWindow* aWindow,
                      HTMLCanvasElement* aCanvas);
 
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -600,16 +600,18 @@ MediaFormatReader::RequestVideoData(bool
 
   if (mShutdown) {
     NS_WARNING("RequestVideoData on shutdown MediaFormatReader!");
     return VideoDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)};
   if (ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
+    // Cancel any pending demux request.
+    mVideo.mDemuxRequest.DisconnectIfExists();
     Flush(TrackInfo::kVideoTrack);
     nsRefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
     SkipVideoDemuxToNextKeyFrame(timeThreshold);
     return p;
   }
 
   nsRefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
   NotifyDecodingRequested(TrackInfo::kVideoTrack);
@@ -967,16 +969,17 @@ MediaFormatReader::DecodeDemuxedSamples(
         decoder.mQueuedSamples.AppendElements(Move(samples));
         NotifyDecodingRequested(aTrack);
       } else {
         MOZ_ASSERT(decoder.mTimeThreshold.isNothing());
         LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
             sample->mTime);
         decoder.mTimeThreshold = Some(TimeUnit::FromMicroseconds(sample->mTime));
         nsRefPtr<MediaFormatReader> self = this;
+        decoder.ResetDemuxer();
         decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref())
                    ->Then(OwnerThread(), __func__,
                           [self, aTrack] (media::TimeUnit aTime) {
                             auto& decoder = self->GetDecoderData(aTrack);
                             decoder.mSeekRequest.Complete();
                             self->NotifyDecodingRequested(aTrack);
                           },
                           [self, aTrack] (DemuxerFailureReason aResult) {
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -79,16 +79,17 @@ MediaKeySystemAccessManager::Request(Det
 
 static bool
 ShouldTrialCreateGMP(const nsAString& aKeySystem)
 {
   // Trial create where the CDM has a decoder;
   // * ClearKey and Primetime on Windows Vista and later.
   // * Primetime on MacOSX Lion and later.
   return
+    Preferences::GetBool("media.gmp.trial-create.enabled", false) &&
 #ifdef XP_WIN
     IsVistaOrLater();
 #elif defined(XP_MACOSX)
     aKeySystem.EqualsLiteral("com.adobe.primetime") &&
     nsCocoaFeatures::OnLionOrLater();
 #else
     false;
 #endif
--- a/dom/media/systemservices/CamerasParent.cpp
+++ b/dom/media/systemservices/CamerasParent.cpp
@@ -761,16 +761,28 @@ CamerasParent::RecvStopCapture(const int
       }
       return NS_OK;
     });
 
   mVideoCaptureThread->message_loop()->PostTask(FROM_HERE, new RunnableTask(webrtc_runnable));
   return SendReplySuccess();
 }
 
+void
+CamerasParent::StopIPC()
+{
+  MOZ_ASSERT(!mDestroyed);
+  // Release shared memory now, it's our last chance
+  mShmemPool.Cleanup(this);
+  // We don't want to receive callbacks or anything if we can't
+  // forward them anymore anyway.
+  mChildIsAlive = false;
+  mDestroyed = true;
+}
+
 bool
 CamerasParent::RecvAllDone()
 {
   LOG((__PRETTY_FUNCTION__));
   // Don't try to send anything to the child now
   mChildIsAlive = false;
   return Send__delete__(this);
 }
@@ -786,38 +798,33 @@ void CamerasParent::DoShutdown()
       if (mEngines[i].mEngine) {
         mEngines[i].mEngine->SetTraceCallback(nullptr);
         webrtc::VideoEngine::Delete(mEngines[i].mEngine);
         mEngines[i].mEngine = nullptr;
       }
     }
   }
 
-  mShmemPool.Cleanup(this);
-
   mPBackgroundThread = nullptr;
 
   if (mVideoCaptureThread) {
     if (mVideoCaptureThread->IsRunning()) {
       mVideoCaptureThread->Stop();
     }
     delete mVideoCaptureThread;
     mVideoCaptureThread = nullptr;
   }
 }
 
 void
 CamerasParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   // No more IPC from here
   LOG((__PRETTY_FUNCTION__));
-  // We don't want to receive callbacks or anything if we can't
-  // forward them anymore anyway.
-  mChildIsAlive = false;
-  mDestroyed = true;
+  StopIPC();
   CloseEngines();
 }
 
 CamerasParent::CamerasParent()
   : mCallbackMutex("CamerasParent.mCallbackMutex"),
     mEngineMutex("CamerasParent.mEngineMutex"),
     mShmemPool(CaptureEngine::MaxEngine),
     mVideoCaptureThread(nullptr),
--- a/dom/media/systemservices/CamerasParent.h
+++ b/dom/media/systemservices/CamerasParent.h
@@ -112,16 +112,17 @@ public:
 
 protected:
   virtual ~CamerasParent();
 
   bool SetupEngine(CaptureEngine aCapEngine);
   void CloseEngines();
   bool EnsureInitialized(int aEngine);
   void DoShutdown();
+  void StopIPC();
 
   EngineHelper mEngines[CaptureEngine::MaxEngine];
   nsTArray<CallbackHelper*> mCallbacks;
   // Protects the callback arrays
   Mutex mCallbackMutex;
   // Protects the engines array
   Mutex mEngineMutex;
 
--- a/dom/media/systemservices/ShmemPool.cpp
+++ b/dom/media/systemservices/ShmemPool.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Logging.h"
 #include "mozilla/ShmemPool.h"
 #include "mozilla/Move.h"
 
 #undef LOG
 #undef LOG_ENABLED
+extern PRLogModuleInfo *gCamerasParentLog;
 #define LOG(args) MOZ_LOG(gCamerasParentLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasParentLog, mozilla::LogLevel::Debug)
 
 namespace mozilla {
 
 ShmemPool::ShmemPool(size_t aPoolSize)
   : mMutex("mozilla::ShmemPool"),
     mPoolFree(aPoolSize)
--- a/dom/media/systemservices/moz.build
+++ b/dom/media/systemservices/moz.build
@@ -1,31 +1,31 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 if CONFIG['MOZ_WEBRTC']:
-    EXPORTS += ['CamerasChild.h',
-                'CamerasParent.h',
-                'CamerasUtils.h',
-                'LoadManager.h',
-                'LoadManagerFactory.h',
-                'LoadMonitor.h',
+    EXPORTS += [
+        'CamerasChild.h',
+        'CamerasParent.h',
+        'CamerasUtils.h',
+        'LoadManager.h',
+        'LoadManagerFactory.h',
+        'LoadMonitor.h',
     ]
-    UNIFIED_SOURCES += ['CamerasChild.cpp',
-                        'CamerasParent.cpp',
-                        'CamerasUtils.cpp',
-                        'LoadManager.cpp',
-                        'LoadManagerFactory.cpp',
-                        'LoadMonitor.cpp',
-    ]
-    IPDL_SOURCES = [
-        'PCameras.ipdl',
+    UNIFIED_SOURCES += [
+        'CamerasChild.cpp',
+        'CamerasParent.cpp',
+        'CamerasUtils.cpp',
+        'LoadManager.cpp',
+        'LoadManagerFactory.cpp',
+        'LoadMonitor.cpp',
+        'ShmemPool.cpp',
     ]
     LOCAL_INCLUDES += [
         '/media/webrtc/signaling',
         '/media/webrtc/trunk',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gonk'):
     EXPORTS += [
@@ -58,27 +58,28 @@ EXPORTS.mozilla.media += ['MediaChild.h'
                           'MediaSystemResourceManager.h',
                           'MediaSystemResourceManagerChild.h',
                           'MediaSystemResourceManagerParent.h',
                           'MediaSystemResourceMessageUtils.h',
                           'MediaSystemResourceService.h',
                           'MediaSystemResourceTypes.h',
                           'MediaUtils.h',
 ]
-UNIFIED_SOURCES += ['MediaChild.cpp',
-                    'MediaParent.cpp',
-                    'MediaSystemResourceClient.cpp',
-                    'MediaSystemResourceManager.cpp',
-                    'MediaSystemResourceManagerChild.cpp',
-                    'MediaSystemResourceManagerParent.cpp',
-                    'MediaSystemResourceService.cpp',
-                    'MediaUtils.cpp',
-                    'ShmemPool.cpp',
+UNIFIED_SOURCES += [
+    'MediaChild.cpp',
+    'MediaParent.cpp',
+    'MediaSystemResourceClient.cpp',
+    'MediaSystemResourceManager.cpp',
+    'MediaSystemResourceManagerChild.cpp',
+    'MediaSystemResourceManagerParent.cpp',
+    'MediaSystemResourceService.cpp',
+    'MediaUtils.cpp',
 ]
 IPDL_SOURCES += [
+    'PCameras.ipdl',
     'PMedia.ipdl',
     'PMediaSystemResourceManager.ipdl',
 ]
 # /dom/base needed for nsGlobalWindow.h in MediaChild.cpp
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
 
--- a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
@@ -13,39 +13,44 @@ createHTML({
   visible: true
 });
 
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
   var vremote;
   var h = new CaptureStreamTestHelper2D();
   var canvas = document.createElement('canvas');
+  var stream;
   canvas.id = 'source_canvas';
   canvas.width = canvas.height = 10;
   document.getElementById('content').appendChild(canvas);
 
   test.setMediaConstraints([{video: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
-    function DRAW_LOCAL_GREEN(test) {
+    function DRAW_INITIAL_LOCAL_GREEN(test) {
       h.drawColor(canvas, h.green);
     },
     function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
-      var stream = canvas.captureStream(10);
+      stream = canvas.captureStream(0);
       test.pcLocal.attachMedia(stream, 'video', 'local');
     }
   ]);
   test.chain.append([
     function FIND_REMOTE_VIDEO() {
       vremote = document.getElementById('pcRemote_remote1_video');
       ok(!!vremote, "Should have remote video element for pcRemote");
     },
     function WAIT_FOR_REMOTE_GREEN() {
       return h.waitForPixel(vremote, h.green, 128, "pcRemote's remote should become green");
     },
     function DRAW_LOCAL_RED() {
+      // After requesting a frame it will be captured at the time of next render.
+      // Next render will happen at next stable state, at the earliest,
+      // i.e., this order of `requestFrame(); draw();` should work.
+      stream.requestFrame();
       h.drawColor(canvas, h.red);
     },
     function WAIT_FOR_REMOTE_RED() {
       return h.waitForPixel(vremote, h.red, 128, "pcRemote's remote should become red");
     }
   ]);
   test.run();
 });
--- a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
@@ -26,19 +26,20 @@ createHTML({
 
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
   var vremote;
   var h = new CaptureStreamTestHelperWebGL();
   var canvas = document.createElement('canvas');
   canvas.id = 'source_canvas';
   canvas.width = canvas.height = 10;
+  canvas.style.display = 'none';
   document.getElementById('content').appendChild(canvas);
 
-  var gl = WebGLUtil.getWebGL(canvas.id, false, { preserveDrawingBuffer: true });
+  var gl = WebGLUtil.getWebGL(canvas.id, false);
   if (!gl) {
     todo(false, "WebGL unavailable.");
     networkTestFinished();
     return;
   }
 
   var errorFunc = str => ok(false, 'Error: ' + str);
   WebGLUtil.setErrorFunc(errorFunc);
@@ -85,22 +86,25 @@ runNetworkTest(() => {
   test.chain.append([
     function FIND_REMOTE_VIDEO() {
       vremote = document.getElementById('pcRemote_remote1_video');
       ok(!!vremote, "Should have remote video element for pcRemote");
     },
     function WAIT_FOR_REMOTE_GREEN() {
       return h.waitForPixel(vremote, h.green, 128, "pcRemote's remote should become green");
     },
+    function REQUEST_FRAME(test) {
+      // After requesting a frame it will be captured at the time of next render.
+      // Next render will happen at next stable state, at the earliest,
+      // i.e., this order of `requestFrame(); draw();` should work.
+      test.pcLocal.canvasStream.requestFrame();
+    },
     function DRAW_LOCAL_RED() {
       h.drawColor(canvas, h.red);
     },
-    function REQUEST_FRAME(test) {
-      test.pcLocal.canvasStream.requestFrame();
-    },
     function WAIT_FOR_REMOTE_RED() {
       return h.waitForPixel(vremote, h.red, 128, "pcRemote's remote should become red");
     }
   ]);
   test.run();
 });
 </script>
 </pre>
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -372,115 +372,131 @@ nsCSPContext::AppendPolicy(const nsAStri
   if (policy) {
     mPolicies.AppendElement(policy);
     // reset cache since effective policy changes
     mShouldLoadCache.Clear();
   }
   return NS_OK;
 }
 
-// aNonceOrContent either holds the nonce-value or otherwise the content
-// of the element to be hashed.
 NS_IMETHODIMP
-nsCSPContext::getAllowsInternal(nsContentPolicyType aContentType,
-                                enum CSPKeyword aKeyword,
-                                const nsAString& aNonceOrContent,
-                                bool* outShouldReportViolation,
-                                bool* outIsAllowed) const
+nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
+                            bool* outAllowsEval)
 {
   *outShouldReportViolation = false;
-  *outIsAllowed = true;
-
-  // Skip things that aren't hash/nonce compatible
-  if (aKeyword == CSP_NONCE || aKeyword == CSP_HASH) {
-    if (!(aContentType == nsIContentPolicy::TYPE_SCRIPT ||
-          aContentType == nsIContentPolicy::TYPE_STYLESHEET)) {
-      *outIsAllowed = false;
-      return NS_OK;
-    }
-  }
+  *outAllowsEval = true;
 
   for (uint32_t i = 0; i < mPolicies.Length(); i++) {
-    if (!mPolicies[i]->allows(aContentType,
-                              aKeyword,
-                              aNonceOrContent)) {
+    if (!mPolicies[i]->allows(nsIContentPolicy::TYPE_SCRIPT,
+                              CSP_UNSAFE_EVAL, EmptyString())) {
       // policy is violated: must report the violation and allow the inline
       // script if the policy is report-only.
       *outShouldReportViolation = true;
       if (!mPolicies[i]->getReportOnlyFlag()) {
-        *outIsAllowed = false;
+        *outAllowsEval = false;
       }
     }
   }
-  CSPCONTEXTLOG(("nsCSPContext::getAllowsInternal, aContentType: %d, aKeyword: %s, aNonceOrContent: %s, isAllowed: %s",
-                aContentType,
-                aKeyword == CSP_HASH ? "hash" : CSP_EnumToKeyword(aKeyword),
-                NS_ConvertUTF16toUTF8(aNonceOrContent).get(),
-                *outIsAllowed ? "load" : "deny"));
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsCSPContext::GetAllowsInlineScript(bool* outShouldReportViolation,
-                                    bool* outAllowsInlineScript)
+// Helper function to report inline violations
+void
+nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType,
+                                    const nsAString& aNonce,
+                                    const nsAString& aContent,
+                                    const nsAString& aViolatedDirective,
+                                    uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that
+                                    uint32_t aLineNumber)
 {
-  return getAllowsInternal(nsIContentPolicy::TYPE_SCRIPT,
-                           CSP_UNSAFE_INLINE,
-                           EmptyString(),
-                           outShouldReportViolation,
-                           outAllowsInlineScript);
-}
+  nsString observerSubject;
+  // if the nonce is non empty, then we report the nonce error, otherwise
+  // let's report the hash error; no need to report the unsafe-inline error
+  // anymore.
+  if (!aNonce.IsEmpty()) {
+    observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
+                      ? NS_LITERAL_STRING(SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC)
+                      : NS_LITERAL_STRING(STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
+  }
+  else {
+    observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
+                      ? NS_LITERAL_STRING(SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC)
+                      : NS_LITERAL_STRING(STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
+  }
 
-NS_IMETHODIMP
-nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
-                            bool* outAllowsEval)
-{
-  return getAllowsInternal(nsIContentPolicy::TYPE_SCRIPT,
-                           CSP_UNSAFE_EVAL,
-                           EmptyString(),
-                           outShouldReportViolation,
-                           outAllowsEval);
+  nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+  if (selfICString) {
+    selfICString->SetData(nsDependentCString("self"));
+  }
+  nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
+
+  // use selfURI as the sourceFile
+  nsAutoCString sourceFile;
+  mSelfURI->GetSpec(sourceFile);
+
+  nsAutoString codeSample(aContent);
+  // cap the length of the script sample at 40 chars
+  if (codeSample.Length() > 40) {
+    codeSample.Truncate(40);
+    codeSample.AppendLiteral("...");
+  }
+  AsyncReportViolation(selfISupports,                      // aBlockedContentSource
+                       mSelfURI,                           // aOriginalURI
+                       aViolatedDirective,                 // aViolatedDirective
+                       aViolatedPolicyIndex,               // aViolatedPolicyIndex
+                       observerSubject,                    // aObserverSubject
+                       NS_ConvertUTF8toUTF16(sourceFile),  // aSourceFile
+                       codeSample,                         // aScriptSample
+                       aLineNumber);                       // aLineNum
 }
 
 NS_IMETHODIMP
-nsCSPContext::GetAllowsInlineStyle(bool* outShouldReportViolation,
-                                   bool* outAllowsInlineStyle)
+nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
+                              const nsAString& aNonce,
+                              const nsAString& aContent,
+                              uint32_t aLineNumber,
+                              bool* outAllowsInline)
 {
-  return getAllowsInternal(nsIContentPolicy::TYPE_STYLESHEET,
-                           CSP_UNSAFE_INLINE,
-                           EmptyString(),
-                           outShouldReportViolation,
-                           outAllowsInlineStyle);
+  *outAllowsInline = true;
+
+  MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+             "We should only see external content policy types here.");
+
+  if (aContentType != nsIContentPolicy::TYPE_SCRIPT &&
+      aContentType != nsIContentPolicy::TYPE_STYLESHEET) {
+    MOZ_ASSERT(false, "can only allow inline for script or style");
+    return NS_OK;
+  }
+
+  // always iterate all policies, otherwise we might not send out all reports
+  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+    bool allowed =
+      mPolicies[i]->allows(aContentType, CSP_UNSAFE_INLINE, EmptyString()) ||
+      mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce) ||
+      mPolicies[i]->allows(aContentType, CSP_HASH, aContent);
+
+    if (!allowed) {
+      // policy is violoated: deny the load unless policy is report only and
+      // report the violation.
+      if (!mPolicies[i]->getReportOnlyFlag()) {
+        *outAllowsInline = false;
+      }
+      nsAutoString violatedDirective;
+      mPolicies[i]->getDirectiveStringForContentType(aContentType, violatedDirective);
+      reportInlineViolation(aContentType,
+                            aNonce,
+                            aContent,
+                            violatedDirective,
+                            i,
+                            aLineNumber);
+    }
+  }
+  return NS_OK;
 }
 
-NS_IMETHODIMP
-nsCSPContext::GetAllowsNonce(const nsAString& aNonce,
-                             uint32_t aContentType,
-                             bool* outShouldReportViolation,
-                             bool* outAllowsNonce)
-{
-  return getAllowsInternal(aContentType,
-                           CSP_NONCE,
-                           aNonce,
-                           outShouldReportViolation,
-                           outAllowsNonce);
-}
-
-NS_IMETHODIMP
-nsCSPContext::GetAllowsHash(const nsAString& aContent,
-                            uint16_t aContentType,
-                            bool* outShouldReportViolation,
-                            bool* outAllowsHash)
-{
-  return getAllowsInternal(aContentType,
-                           CSP_HASH,
-                           aContent,
-                           outShouldReportViolation,
-                           outAllowsHash);
-}
 
 /**
  * Reduces some code repetition for the various logging situations in
  * LogViolationDetails.
  *
  * Call-sites for the eval/inline checks recieve two return values: allows
  * and violates.  Based on those, they must choose whether to call
  * LogViolationDetails or not.  Policies that are report-only allow the
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -53,32 +53,34 @@ class nsCSPContext : public nsIContentSe
                                   const nsAString& aViolatedDirective,
                                   uint32_t aViolatedPolicyIndex,
                                   const nsAString& aObserverSubject,
                                   const nsAString& aSourceFile,
                                   const nsAString& aScriptSample,
                                   uint32_t aLineNum);
 
   private:
-    NS_IMETHODIMP getAllowsInternal(nsContentPolicyType aContentType,
-                                    enum CSPKeyword aKeyword,
-                                    const nsAString& aNonceOrContent,
-                                    bool* outShouldReportViolations,
-                                    bool* outIsAllowed) const;
-
     bool permitsInternal(CSPDirective aDir,
                          nsIURI* aContentLocation,
                          nsIURI* aOriginalURI,
                          const nsAString& aNonce,
                          bool aWasRedirected,
                          bool aIsPreload,
                          bool aSpecific,
                          bool aSendViolationReports,
                          bool aSendContentLocationInViolationReports);
 
+    // helper to report inline script/style violations
+    void reportInlineViolation(nsContentPolicyType aContentType,
+                               const nsAString& aNonce,
+                               const nsAString& aContent,
+                               const nsAString& aViolatedDirective,
+                               uint32_t aViolatedPolicyIndex,
+                               uint32_t aLineNumber);
+
     nsCOMPtr<nsIURI>                           mReferrer;
     uint64_t                                   mInnerWindowID; // used for web console logging
     nsTArray<nsCSPPolicy*>                     mPolicies;
     nsCOMPtr<nsIURI>                           mSelfURI;
     nsDataHashtable<nsCStringHashKey, int16_t> mShouldLoadCache;
     nsCOMPtr<nsILoadGroup>                     mCallingChannelLoadGroup;
     nsWeakPtr                                  mLoadingContext;
 };
--- a/dom/security/test/unit/test_csp_reports.js
+++ b/dom/security/test/unit/test_csp_reports.js
@@ -96,31 +96,24 @@ function makeTest(id, expectedJSON, useR
 function run_test() {
   var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
                                ":" + REPORT_SERVER_PORT +
                                "/foo/self");
 
   // test that inline script violations cause a report.
   makeTest(0, {"blocked-uri": "self"}, false,
       function(csp) {
-        let inlineOK = true, oReportViolation = {'value': false};
-        inlineOK = csp.getAllowsInlineScript(oReportViolation);
+        let inlineOK = true;
+        inlineOK = csp.getAllowsInline(Ci.nsIContentPolicy.TYPE_SCRIPT,
+                                       "", // aNonce
+                                       "", // aContent
+                                       0); // aLineNumber
 
         // this is not a report only policy, so it better block inline scripts
         do_check_false(inlineOK);
-        // ... and cause reports to go out
-        do_check_true(oReportViolation.value);
-
-        if (oReportViolation.value) {
-          // force the logging, since the getter doesn't.
-          csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
-                                  selfuri.asciiSpec,
-                                  "script sample",
-                                  0);
-        }
       });
 
   // test that eval violations cause a report.
   makeTest(1, {"blocked-uri": "self"}, false,
       function(csp) {
         let evalOK = true, oReportViolation = {'value': false};
         evalOK = csp.getAllowsEval(oReportViolation);
 
@@ -144,32 +137,24 @@ function run_test() {
         csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
                       NetUtil.newURI("http://blocked.test/foo.js"),
                       null, null, null, null);
       });
 
   // test that inline script violations cause a report in report-only policy
   makeTest(3, {"blocked-uri": "self"}, true,
       function(csp) {
-        let inlineOK = true, oReportViolation = {'value': false};
-        inlineOK = csp.getAllowsInlineScript(oReportViolation);
+        let inlineOK = true;
+        inlineOK = csp.getAllowsInline(Ci.nsIContentPolicy.TYPE_SCRIPT,
+                                       "", // aNonce
+                                       "", // aContent
+                                       0); // aLineNumber
 
         // this is a report only policy, so it better allow inline scripts
         do_check_true(inlineOK);
-
-        // ... and cause reports to go out
-        do_check_true(oReportViolation.value);
-
-        if (oReportViolation.value) {
-          // force the logging, since the getter doesn't.
-          csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
-                                  selfuri.asciiSpec,
-                                  "script sample",
-                                  3);
-        }
       });
 
   // test that eval violations cause a report in report-only policy
   makeTest(4, {"blocked-uri": "self"}, true,
       function(csp) {
         let evalOK = true, oReportViolation = {'value': false};
         evalOK = csp.getAllowsEval(oReportViolation);
 
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -615,17 +615,17 @@ nsXULElement::IsFocusableInternal(int32_
     } else {
       shouldFocus = *aTabIndex >= 0;
     }
   }
 
   return shouldFocus;
 }
 
-void
+bool
 nsXULElement::PerformAccesskey(bool aKeyCausesActivation,
                                bool aIsTrustedEvent)
 {
     nsCOMPtr<nsIContent> content(this);
 
     if (IsXULElement(nsGkAtoms::label)) {
         nsCOMPtr<nsIDOMElement> element;
 
@@ -638,57 +638,67 @@ nsXULElement::PerformAccesskey(bool aKey
                 do_QueryInterface(content->GetUncomposedDoc());
             if (domDocument)
                 domDocument->GetElementById(control, getter_AddRefs(element));
         }
         // here we'll either change |content| to the element referenced by
         // |element|, or clear it.
         content = do_QueryInterface(element);
 
-        if (!content)
-            return;
+        if (!content) {
+            return false;
+        }
     }
 
     nsIFrame* frame = content->GetPrimaryFrame();
-    if (!frame || !frame->IsVisibleConsideringAncestors())
-        return;
-
+    if (!frame || !frame->IsVisibleConsideringAncestors()) {
+        return false;
+    }
+
+    bool focused = false;
     nsXULElement* elm = FromContent(content);
     if (elm) {
         // Define behavior for each type of XUL element.
         if (!content->IsXULElement(nsGkAtoms::toolbarbutton)) {
           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
           if (fm) {
-            nsCOMPtr<nsIDOMElement> element;
+            nsCOMPtr<nsIDOMElement> elementToFocus;
             // for radio buttons, focus the radiogroup instead
             if (content->IsXULElement(nsGkAtoms::radio)) {
               nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem(do_QueryInterface(content));
               if (controlItem) {
                 bool disabled;
                 controlItem->GetDisabled(&disabled);
                 if (!disabled) {
                   nsCOMPtr<nsIDOMXULSelectControlElement> selectControl;
                   controlItem->GetControl(getter_AddRefs(selectControl));
-                  element = do_QueryInterface(selectControl);
+                  elementToFocus = do_QueryInterface(selectControl);
                 }
               }
             } else {
-              element = do_QueryInterface(content);
+              elementToFocus = do_QueryInterface(content);
             }
-            if (element)
-              fm->SetFocus(element, nsIFocusManager::FLAG_BYKEY);
+            if (elementToFocus) {
+              fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);
+
+              // Return true if the element became focused.
+              nsPIDOMWindow* window = OwnerDoc()->GetWindow();
+              focused = (window && window->GetFocusedNode());
+            }
           }
         }
         if (aKeyCausesActivation &&
             !content->IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::menulist)) {
           elm->ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD);