Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 01 May 2015 12:59:30 -0400
changeset 273404 927a1934960e77d45a04bf2febfb19390956c043
parent 273403 6f5fc21d5ea60ed8707fed65a32f45582414b417 (current diff)
parent 273354 e70555ac58d8a6b15efe667bb419726849b1cfc3 (diff)
child 273405 320458ef6d17685999d666c46b5cc6dd6c02f8c4
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.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 b2g-inbound. a=merge
browser/components/loop/CardDavImporter.jsm
browser/components/loop/GoogleImporter.jsm
browser/components/loop/LoopCalls.jsm
browser/components/loop/LoopContacts.jsm
browser/components/loop/LoopRooms.jsm
browser/components/loop/LoopStorage.jsm
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/MozLoopPushHandler.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/MozLoopWorker.js
browser/components/loop/content/shared/.eslintrc
browser/components/loop/standalone/.eslintrc
browser/devtools/performance/test/browser_timeline_blueprint.js
browser/devtools/performance/test/browser_timeline_filters.js
browser/devtools/timeline/test/browser.ini
browser/devtools/timeline/test/browser_timeline_aaa_run_first_leaktest.js
browser/devtools/timeline/test/browser_timeline_overview-initial-selection-01.js
browser/devtools/timeline/test/browser_timeline_overview-initial-selection-02.js
browser/devtools/timeline/test/browser_timeline_overview-theme.js
browser/devtools/timeline/test/browser_timeline_overview-update.js
browser/devtools/timeline/test/browser_timeline_panels.js
browser/devtools/timeline/test/browser_timeline_recording-without-memory.js
browser/devtools/timeline/test/browser_timeline_recording.js
browser/devtools/timeline/test/browser_timeline_waterfall-background.js
browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
browser/devtools/timeline/test/browser_timeline_waterfall-sidebar.js
browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
browser/devtools/timeline/test/doc_simple-test.html
browser/devtools/timeline/test/head.js
dom/media/fmp4/MP4Reader.cpp
toolkit/themes/windows/mozapps/extensions/navigation.png
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -1036,16 +1036,28 @@ GetWrapperFor(ProxyAccessible* aProxy)
 static uint16_t
 GetInterfacesForProxy(ProxyAccessible* aProxy, uint32_t aInterfaces)
 {
   uint16_t interfaces = 1 << MAI_INTERFACE_COMPONENT;
   if (aInterfaces & Interfaces::HYPERTEXT)
     interfaces |= (1 << MAI_INTERFACE_HYPERTEXT) | (1 << MAI_INTERFACE_TEXT)
         | (1 << MAI_INTERFACE_EDITABLE_TEXT);
 
+  if (aInterfaces & Interfaces::HYPERLINK)
+    interfaces |= MAI_INTERFACE_HYPERLINK_IMPL;
+
+  if (aInterfaces & Interfaces::VALUE)
+    interfaces |= MAI_INTERFACE_VALUE;
+
+  if (aInterfaces & Interfaces::TABLE)
+    interfaces |= MAI_INTERFACE_TABLE;
+
+  if (aInterfaces & Interfaces::IMAGE)
+    interfaces |= MAI_INTERFACE_IMAGE;
+
   return interfaces;
 }
 
 void
 a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
 {
   GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy, aInterfaces));
   NS_ASSERTION(type, "why don't we have a type!");
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -22,16 +22,28 @@ namespace a11y {
 
 static uint32_t
 InterfacesFor(Accessible* aAcc)
 {
   uint32_t interfaces = 0;
   if (aAcc->IsHyperText() && aAcc->AsHyperText()->IsTextRole())
     interfaces |= Interfaces::HYPERTEXT;
 
+  if (aAcc->IsLink())
+    interfaces |= Interfaces::HYPERLINK;
+
+  if (aAcc->HasNumericValue())
+    interfaces |= Interfaces::VALUE;
+
+  if (aAcc->IsImage())
+    interfaces |= Interfaces::IMAGE;
+
+  if (aAcc->IsTableCell())
+    interfaces |= Interfaces::TABLECELL;
+
   return interfaces;
 }
 
 static void
 SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree)
 {
   uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID());
   uint32_t role = aRoot->Role();
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -309,15 +309,20 @@ private:
   uintptr_t mWrapper;
   uint64_t mID;
   role mRole : 31;
   bool mOuterDoc : 1;
 };
 
 enum Interfaces
 {
-  HYPERTEXT = 1
+  HYPERTEXT = 1,
+  HYPERLINK = 2,
+  IMAGE = 4,
+  VALUE = 8,
+  TABLE = 16,
+  TABLECELL = 32,
 };
 
 }
 }
 
 #endif
--- a/accessible/windows/msaa/Compatibility.h
+++ b/accessible/windows/msaa/Compatibility.h
@@ -34,16 +34,36 @@ public:
    */
   static bool IsWE() { return !!(sConsumers & WE); }
 
   /**
    * Return true if Dolphin mode is enabled.
    */
   static bool IsDolphin() { return !!(sConsumers & DOLPHIN); }
 
+  /**
+   * Return true if we should disable e10s due to a detected
+   * accessibility client.
+   */
+  static bool IsBlacklistedForE10S()
+  {
+    // We currently blacklist everything except UNKNOWN and UIAUTOMATION
+    return !!(sConsumers &
+              (NVDA     |
+               JAWS     |
+               OLDJAWS  |
+               WE       |
+               DOLPHIN  |
+               SEROTEK  |
+               COBRA    |
+               ZOOMTEXT |
+               KAZAGURU |
+               YOUDAO));
+  }
+
 private:
   Compatibility();
   Compatibility(const Compatibility&);
   Compatibility& operator = (const Compatibility&);
 
   /**
    * Initialize compatibility mode. Called by platform (see Platform.h) during
    * accessibility initialization.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1126,8 +1126,12 @@ pref("dom.mozSettings.SettingsService.ve
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
 // RequestSync API is enabled by default on B2G.
 pref("dom.requestSync.enabled", true);
 
 // Resample touch events on b2g
 pref("gfx.touch.resample", true);
+
+// mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly
+// disable serviceworkers here to get them disabled in mulet.
+pref("dom.serviceWorkers.enabled", false);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1322,18 +1322,18 @@ pref("services.sync.prefs.sync.security.
 pref("services.sync.prefs.sync.security.tls.version.max", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Developer edition preferences
 #ifdef MOZ_DEV_EDITION
-pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
-pref("browser.devedition.theme.enabled", true);
+sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
+sticky_pref("browser.devedition.theme.enabled", true);
 #endif
 
 // Developer edition promo preferences
 pref("devtools.devedition.promo.shown", false);
 pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
 
 // Only potentially show in beta release
 #if MOZ_UPDATE_CHANNEL == beta
@@ -1438,16 +1438,25 @@ pref("devtools.performance.profiler.samp
 pref("devtools.performance.ui.invert-call-tree", true);
 pref("devtools.performance.ui.invert-flame-graph", false);
 pref("devtools.performance.ui.flatten-tree-recursion", true);
 pref("devtools.performance.ui.show-platform-data", false);
 pref("devtools.performance.ui.show-idle-blocks", true);
 pref("devtools.performance.ui.enable-memory", false);
 pref("devtools.performance.ui.enable-framerate", true);
 pref("devtools.performance.ui.show-jit-optimizations", false);
+// If in aurora (40.0, will revert for 40.1), set default
+// to retro mode.
+// TODO bug 1160313
+#if MOZ_UPDATE_CHANNEL == aurora
+  pref("devtools.performance.ui.retro-mode", true);
+#else
+  pref("devtools.performance.ui.retro-mode", false);
+#endif
+
 
 // The default cache UI setting
 pref("devtools.cache.disabled", false);
 
 // The default service workers UI setting
 pref("devtools.serviceWorkers.testing.enabled", false);
 
 // Enable the Network Monitor
@@ -1502,19 +1511,19 @@ pref("devtools.canvasdebugger.enabled", 
 // Enable the Web Audio Editor
 pref("devtools.webaudioeditor.enabled", false);
 
 // Web Audio Editor Inspector Width should be a preference
 pref("devtools.webaudioeditor.inspectorWidth", 300);
 
 // Default theme ("dark" or "light")
 #ifdef MOZ_DEV_EDITION
-pref("devtools.theme", "dark");
+sticky_pref("devtools.theme", "dark");
 #else
-pref("devtools.theme", "light");
+sticky_pref("devtools.theme", "light");
 #endif
 
 // Display the introductory text
 pref("devtools.gcli.hideIntro", false);
 
 // How eager are we to show help: never=1, sometimes=2, always=3
 pref("devtools.gcli.eagerHelper", 2);
 
@@ -1641,20 +1650,20 @@ pref("browser.newtabpage.enabled", true)
 
 // number of rows of newtab grid
 pref("browser.newtabpage.rows", 3);
 
 // number of columns of newtab grid
 pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
-pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v2/links/fetch/%LOCALE%");
+pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
 
 // endpoint to send newtab click and view pings
-pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v2/links/");
+pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // True if the fullscreen API requires approval upon a domain entering fullscreen.
 // Domains that have already had fullscreen permission granted won't re-request
 // approval.
 pref("full-screen-api.approval-required", true);
@@ -1858,25 +1867,25 @@ pref("media.gmp-provider.enabled", true)
 
 pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
 
 #ifdef NIGHTLY_BUILD
 pref("browser.polaris.enabled", false);
 pref("privacy.trackingprotection.ui.enabled", false);
 #endif
 
-#ifdef NIGHTLY_BUILD
+#ifdef E10S_TESTING_ONLY
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
 #endif
 
-#ifdef NIGHTLY_BUILD
+#ifdef E10S_TESTING_ONLY
 // Enable e10s add-on interposition by default.
 pref("extensions.interposition.enabled", true);
 pref("extensions.interposition.prefetching", true);
 #endif
 
 pref("browser.defaultbrowser.notificationbar", false);
 
 // How often to check for CPOW timeouts. CPOWs are only timed out by
@@ -1895,16 +1904,17 @@ pref("dom.ipc.reportProcessHangs", false
 pref("dom.ipc.reportProcessHangs", true);
 #endif
 
 pref("browser.readinglist.enabled", false);
 pref("browser.readinglist.sidebarEverOpened", false);
 pref("readinglist.scheduler.enabled", false);
 pref("readinglist.server", "https://readinglist.services.mozilla.com/v1");
 
+pref("browser.reader.detectedFirstArticle", false);
 // Don't limit how many nodes we care about on desktop:
 pref("reader.parse-node-limit", 0);
 
 // Enable Service workers for desktop on non-release builds
 #ifdef NIGHTLY_BUILD
 pref("dom.serviceWorkers.enabled", true);
 #endif
 
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -96,27 +96,29 @@ let handleContentContextMenu = function 
 
   // Media related cache info parent needs for saving
   let contentType = null;
   let contentDisposition = null;
   if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
       event.target instanceof Ci.nsIImageLoadingContent &&
       event.target.currentURI) {
     try {
-      let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                                                       .getImgCacheForDocument(doc);
+      let imageCache = 
+        Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+                                        .getImgCacheForDocument(doc);
       let props =
         imageCache.findEntryProperties(event.target.currentURI);
-      if (props) {
+      try {
         contentType = props.get("type", Ci.nsISupportsCString).data;
-        contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
-      }
-    } catch (e) {
-      Cu.reportError(e);
-    }
+      } catch(e) {}
+      try {
+        contentDisposition =
+          props.get("content-disposition", Ci.nsISupportsCString).data;
+      } catch(e) {}
+    } catch(e) {}
   }
 
   let selectionInfo = BrowserUtils.getSelectionDetails(content);
 
   if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
     let editFlags = SpellCheckHelper.isEditable(event.target, content);
     let spellInfo;
     if (editFlags &
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -128,17 +128,17 @@ Site.prototype = {
     let link = this._querySelector(".newtab-link");
     link.setAttribute("title", tooltip);
     link.setAttribute("href", url);
     this._querySelector(".newtab-title").textContent = title;
     this.node.setAttribute("type", this.link.type);
 
     if (this.link.targetedSite) {
       this.node.setAttribute("suggested", true);
-      let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
+      let targetedSite = `<strong> ${this.link.targetedName} </strong>`;
       this._querySelector(".newtab-suggested").innerHTML =
         `<div class='newtab-suggested-bounds'> ${newTabString("suggested.button", [targetedSite])} </div>`;
     }
 
     if (this.isPinned())
       this._updateAttributes(true);
     // Capture the page if the thumbnail is missing, which will cause page.js
     // to be notified and call our refreshThumbnail() method.
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -550,17 +550,19 @@ function gKeywordURIFixup(fixupInfo) {
   sendAsyncMessage("Browser:URIFixup", data);
 }
 Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup", false);
 addEventListener("unload", () => {
   Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
 }, false);
 
 addMessageListener("Browser:AppTab", function(message) {
-  docShell.isAppTab = message.data.isAppTab;
+  if (docShell) {
+    docShell.isAppTab = message.data.isAppTab;
+  }
 });
 
 let WebBrowserChrome = {
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
   },
 
   // Check whether this URI should load in the current process
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1030,18 +1030,22 @@
         <body>
           <![CDATA[
             var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
             if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
               return;
 
             if (!aForceUpdate) {
               TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
-              window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
-                                                             .beginTabSwitch();
+              if (!Services.appinfo.browserTabsRemoteAutostart) {
+                // old way of measuring tab paint which is not
+                // valid with e10s.
+                window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+                                                               .beginTabSwitch();
+              }
             }
 
             var oldTab = this.mCurrentTab;
 
             // Preview mode should not reset the owner
             if (!this._previewMode && !oldTab.selected)
               oldTab.owner = null;
 
@@ -2980,18 +2984,16 @@
             // removed from the set upon MozAfterPaint.
             maybeVisibleTabs: new Set([this.selectedTab]),
 
             STATE_UNLOADED: 0,
             STATE_LOADING: 1,
             STATE_LOADED: 2,
             STATE_UNLOADING: 3,
 
-            logging: false,
-
             getTabState: function(tab) {
               let state = this.tabState.get(tab);
               if (state === undefined) {
                 return this.STATE_UNLOADED;
               }
               return state;
             },
 
@@ -3083,26 +3085,28 @@
               } else {
                 // Show the requested tab. If it's not available, we'll show the spinner.
                 showTab = this.requestedTab;
               }
 
               // Show or hide the spinner as needed.
               let needSpinner = this.getTabState(showTab) != this.STATE_LOADED;
               if (!needSpinner && this.spinnerTab) {
+                this.spinnerHidden();
                 this.tabbrowser.removeAttribute("pendingpaint");
                 this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 this.spinnerTab = null;
               } else if (needSpinner && this.spinnerTab !== showTab) {
                 if (this.spinnerTab) {
                   this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 }
                 this.spinnerTab = showTab;
                 this.tabbrowser.setAttribute("pendingpaint", "true");
                 this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
+                this.spinnerDisplayed();
               }
 
               // Switch to the tab we've decided to make visible.
               if (this.visibleTab !== showTab) {
                 this.visibleTab = showTab;
 
                 this.maybeVisibleTabs.add(showTab);
 
@@ -3275,16 +3279,17 @@
               }
             },
 
             // Fires when we paint the screen. Any tab switches we initiated
             // previously are done, so there's no need to keep the old layers
             // around.
             onPaint: function() {
               this.maybeVisibleTabs.clear();
+              this.finishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared: function(browser) {
               this.logState("onLayersCleared");
 
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
@@ -3308,16 +3313,17 @@
 
             // Called when the user asks to switch to a given tab.
             requestTab: function(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
               this.logState("requestTab " + this.tinfo(tab));
+              this.startTabSwitch();
 
               this.requestedTab = tab;
 
               this.preActions();
 
               clearTimeout(this.unloadTimer);
               this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
 
@@ -3335,50 +3341,114 @@
                 this.onLayersCleared(event.originalTarget);
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
               }
 
               this.postActions();
             },
 
+            /*
+             * Telemetry related helpers for recording tab switch timing.
+             */
+
+            startTabSwitch: function () {
+              TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+            },
+
+            finishTabSwitch: function () {
+              if (this.requestedTab && this.getTabState(this.requestedTab) == this.STATE_LOADED) {
+                let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+                if (time != -1) {
+                  TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+                  this.log("DEBUG: tab switch time = " + time);
+                }
+              }
+            },
+
+            spinnerDisplayed: function () {
+              if (this.spinnerTab) {
+                TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+              }
+            },
+
+            spinnerHidden: function () {
+              if (this.spinnerTab) {
+                this.log("DEBUG: spinner time = " +
+                         TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
+                TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+                // we do not get a onPaint after displaying the spinner
+                this.finishTabSwitch();
+              }
+            },
+
+            /*
+             * Debug related logging for switcher.
+             */
+
+            _useDumpForLogging: false,
+            _logInit: false,
+
+            logging: function () {
+              if (this._useDumpForLogging)
+                return true;
+              if (this._logInit)
+                return this._shouldLog;
+              let result = false;
+              try {
+                result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
+              } catch (ex) {
+              }
+              this._shouldLog = result;
+              this._logInit = true;
+              return this._shouldLog;
+            },
+
             tinfo: function(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
               } else {
                 return "null";
               }
             },
 
             log: function(s) {
-              if (!this.logging)
+              if (!this.logging())
                 return;
-              dump(s + "\n");
+              if (this._useDumpForLogging) {
+                dump(s + "\n");
+              } else {
+                Services.console.logStringMessage(s);
+              }
             },
 
             logState: function(prefix) {
-              if (!this.logging)
+              if (!this.logging())
                 return;
 
-              dump(prefix + " ");
+              let accum = prefix + " ";
               for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
                 let tab = this.tabbrowser.tabs[i];
                 let state = this.getTabState(tab);
 
-                dump(i + ":");
+                accum += i + ":";
                 if (tab === this.lastVisibleTab) dump("V");
                 if (tab === this.loadingTab) dump("L");
                 if (tab === this.requestedTab) dump("R");
                 if (state == this.STATE_LOADED) dump("(+)");
                 if (state == this.STATE_LOADING) dump("(+?)");
                 if (state == this.STATE_UNLOADED) dump("(-)");
                 if (state == this.STATE_UNLOADING) dump("(-?)");
-                dump(" ");
+                accum += " ";
               }
-              dump("\n");
+              if (this._useDumpForLogging) {
+                dump(accum + "\n");
+              } else {
+                Services.console.logStringMessage(accum);
+              }
             },
           };
           this._switcher = switcher;
           switcher.init();
           return switcher;
         ]]></body>
       </method>
 
--- a/browser/base/content/test/chat/browser_chatwindow.js
+++ b/browser/base/content/test/chat/browser_chatwindow.js
@@ -1,40 +1,16 @@
 /* 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/Promise.jsm", this);
 
 let chatbar = document.getElementById("pinnedchats");
 
-function waitForCondition(condition, errorMsg) {
-  let deferred = Promise.defer();
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (tries >= 30) {
-      ok(false, errorMsg);
-      moveOn();
-    }
-    var conditionPassed;
-    try {
-      conditionPassed = condition();
-    } catch (e) {
-      ok(false, e + "\n" + e.stack);
-      conditionPassed = false;
-    }
-    if (conditionPassed) {
-      moveOn();
-    }
-    tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); deferred.resolve(); };
-  return deferred.promise;
-}
-
 add_chat_task(function* testOpenCloseChat() {
   let chatbox = yield promiseOpenChat("http://example.com");
   Assert.strictEqual(chatbox, chatbar.selectedChat);
   // we requested a "normal" chat, so shouldn't be minimized
   Assert.ok(!chatbox.minimized, "chat is not minimized");
   Assert.equal(chatbar.childNodes.length, 1, "should be 1 chat open");
 
 
@@ -110,56 +86,48 @@ add_chat_task(function* testSecondTopLev
   yield promiseOneEvent(secondWindow, "load");
   yield promiseOpenChat(chatUrl);
   // the chat was created - let's make sure it was created in the second window.
   Assert.equal(numChatsInWindow(window), 0, "main window has no chats");
   Assert.equal(numChatsInWindow(secondWindow), 1, "second window has 1 chat");
   secondWindow.close();
 });
 
-// Test that chats are created in the correct window.
+// Test that findChromeWindowForChats() returns the expected window.
 add_chat_task(function* testChatWindowChooser() {
   let chat = yield promiseOpenChat("http://example.com");
   Assert.equal(numChatsInWindow(window), 1, "first window has the chat");
   // create a second window - this will be the "most recent" and will
   // therefore be the window that hosts the new chat (see bug 835111)
   let secondWindow = OpenBrowserWindow();
   yield promiseOneEvent(secondWindow, "load");
   Assert.equal(secondWindow, Chat.findChromeWindowForChats(null), "Second window is the preferred chat window");
-  Assert.equal(numChatsInWindow(secondWindow), 0, "second window starts with no chats");
-  yield promiseOpenChat("http://example.com#2");
-  Assert.equal(numChatsInWindow(secondWindow), 1, "second window now has chats");
-  Assert.equal(numChatsInWindow(window), 1, "first window still has 1 chat");
-  chat.close();
 
-  // a bit heavy handed, but handy fixing bug 1090633
-  yield waitForCondition(function () !chat.parentNode, "chat has been detached");
-  Assert.equal(numChatsInWindow(window), 0, "first window now has no chats");
-  // now open another chat - it should still open in the second.
-  yield promiseOpenChat("http://example.com#3");
-  Assert.equal(numChatsInWindow(window), 0, "first window still has no chats");
-  Assert.equal(numChatsInWindow(secondWindow), 2, "second window has both chats");
+  // focus the first window, and check it will be selected for future chats.
+  // Bug 1090633 - there are lots of issues around focus, especially when the
+  // browser itself doesn't have focus. We can end up with
+  // Services.wm.getMostRecentWindow("navigator:browser") returning a different
+  // window than, say, Services.focus.activeWindow. But the focus manager isn't
+  // the point of this test.
+  // So we simply keep focusing the first window until it is reported as the
+  // most recent.
+  do {
+    dump("trying to force window to become the most recent.\n");
+    secondWindow.focus();
+    window.focus();
+    yield promiseWaitForFocus();
+  } while (Services.wm.getMostRecentWindow("navigator:browser") != window)
 
-  // focus the first window, and open yet another chat - it
-  // should open in the first window.
-  window.focus();
-  yield promiseWaitForFocus();
-  chat = yield promiseOpenChat("http://example.com#4");
-  Assert.equal(numChatsInWindow(window), 1, "first window got new chat");
-  chat.close();
-  Assert.equal(numChatsInWindow(window), 0, "first window has no chats");
+  Assert.equal(window, Chat.findChromeWindowForChats(null), "First window now the preferred chat window");
 
   let privateWindow = OpenBrowserWindow({private: true});
   yield promiseOneEvent(privateWindow, "load")
 
-  // open a last chat - the focused window can't accept
-  // chats (it's a private window), so the chat should open
-  // in the window that was selected before. This is known
-  // to be broken on Linux.
-  chat = yield promiseOpenChat("http://example.com#5");
-  let os = Services.appinfo.OS;
-  const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
-  let fn = BROKEN_WM_Z_ORDER ? todo : ok;
-  fn(numChatsInWindow(window) == 1, "first window got the chat");
-  chat.close();
+  // The focused window can't accept chats (it's a private window), so the
+  // chat should open in the window that was selected before. This will be
+  // either window or secondWindow (linux may choose a different one) but the
+  // point is that the window is *not* the private one.
+  Assert.ok(Chat.findChromeWindowForChats(null) == window ||
+            Chat.findChromeWindowForChats(null) == secondWindow,
+            "Private window isn't selected for new chats.");
   privateWindow.close();
   secondWindow.close();
 });
--- a/browser/base/content/test/chat/browser_focus.js
+++ b/browser/base/content/test/chat/browser_focus.js
@@ -5,22 +5,26 @@
 // Tests the focus functionality.
 
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/chat.html";
 
 // Is the currently opened tab focused?
 function isTabFocused() {
   let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
-  return Services.focus.focusedWindow == tabb.contentWindow;
+  // focus sucks in tests - our window may have lost focus.
+  let elt = Services.focus.getFocusedElementForWindow(window, false, {});
+  return elt == tabb;
 }
 
 // Is the specified chat focused?
 function isChatFocused(chat) {
-  return chat.chatbar._isChatFocused(chat);
+  // focus sucks in tests - our window may have lost focus.
+  let elt = Services.focus.getFocusedElementForWindow(window, false, {});
+  return elt == chat.content;
 }
 
 let chatbar = document.getElementById("pinnedchats");
 
 function* setUp() {
   // Note that (probably) due to bug 604289, if a tab is focused but the
   // focused element is null, our chat windows can "steal" focus.  This is
   // avoided if we explicitly focus an element in the tab.
--- a/browser/base/content/test/general/browser_readerMode.js
+++ b/browser/base/content/test/general/browser_readerMode.js
@@ -27,29 +27,39 @@ add_task(function* test_reader_button() 
       gBrowser.removeCurrentTab();
     }
   });
 
   // Set required test prefs.
   TEST_PREFS.forEach(([name, value]) => {
     Services.prefs.setBoolPref(name, value);
   });
+  Services.prefs.setBoolPref("browser.reader.detectedFirstArticle", false);
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   is_element_hidden(readerButton, "Reader mode button is not present on a new tab");
+  ok(!UITour.isInfoOnTarget(window, "readerMode-urlBar"),
+     "Info panel shouldn't appear without the reader mode button");
+  ok(!Services.prefs.getBoolPref("browser.reader.detectedFirstArticle"),
+     "Shouldn't have detected the first article");
 
   // Point tab to a test page that is reader-able.
   let url = TEST_PATH + "readerModeArticle.html";
   yield promiseTabLoadEvent(tab, url);
   yield promiseWaitForCondition(() => !readerButton.hidden);
   is_element_visible(readerButton, "Reader mode button is present on a reader-able page");
+  ok(UITour.isInfoOnTarget(window, "readerMode-urlBar"),
+     "Info panel should be anchored at the reader mode button");
+  ok(Services.prefs.getBoolPref("browser.reader.detectedFirstArticle"),
+     "Should have detected the first article");
 
   // Switch page into reader mode.
   readerButton.click();
   yield promiseTabLoadEvent(tab);
+  ok(!UITour.isInfoOnTarget(window, "readerMode-urlBar"), "Info panel should have closed");
 
   let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
   ok(readerUrl.startsWith("about:reader"), "about:reader loaded after clicking reader mode button");
   is_element_visible(readerButton, "Reader mode button is present on about:reader");
 
   is(gURLBar.value, readerUrl, "gURLBar value is about:reader URL");
   is(gURLBar.textValue, url.substring("http://".length), "gURLBar is displaying original article URL");
 
--- a/browser/base/content/test/newtab/browser_newtab_block.js
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -13,16 +13,18 @@ gDirectorySource = "data:application/jso
     imageURI: "data:image/png;base64,helloWORLD3",
     title: "title",
     type: "affiliate",
     frecent_sites: ["example0.com"]
   }]
 });
 
 function runTests() {
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = (site) => false;
 
   // we remove sites and expect the gaps to be filled as long as there still
   // are some sites available
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   setPinnedLinks("");
 
@@ -79,9 +81,10 @@ function runTests() {
   yield setLinks("0,1,2,3,4,5,6,7,8,9");
   yield addNewTabPageTab();
   yield customizeNewTabPage("enhanced");
   checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
 
   yield blockCell(1);
   yield addNewTabPageTab();
   checkGrid("1,2,3,4,5,6,7,8,9");
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
 }
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -21,18 +21,21 @@ gDirectorySource = "data:application/jso
     imageURI: "data:image/png;base64,helloWORLD3",
     title: "title2",
     type: "affiliate",
     frecent_sites: ["test.com"]
   }]
 });
 
 function runTests() {
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origEnhanced = NewTabUtils.allPages.enhanced;
   registerCleanupFunction(() => {
+    DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
     Services.prefs.clearUserPref(PRELOAD_PREF);
     NewTabUtils.allPages.enhanced = origEnhanced;
   });
 
   Services.prefs.setBoolPref(PRELOAD_PREF, false);
 
   function getData(cellNum) {
     let cell = getCell(cellNum);
--- a/browser/components/loop/.eslintignore
+++ b/browser/components/loop/.eslintignore
@@ -1,15 +1,14 @@
-# We still need to fix warnings in this file.
-MozLoopWorker.js
-# This file currently uses es7 features
-MozLoopAPI.jsm
+# This file currently uses a non-standard (and not on a standards track)
+# if statement within catch.
+modules/MozLoopWorker.js
+# This file currently uses es7 features eslint issue:
+# https://github.com/eslint/espree/issues/125
+modules/MozLoopAPI.jsm
 # Libs we don't need to check
 content/libs
 content/shared/libs
 standalone/content/libs
 standalone/node_modules
-# We should look at turning these on when we fix the warnings
-test/xpcshell
-test/mochitest
 # Libs we don't need to check
 test/shared/vendor
 
--- a/browser/components/loop/.eslintrc
+++ b/browser/components/loop/.eslintrc
@@ -1,27 +1,17 @@
+// Note: there are extra allowances for files used solely in Firefox desktop,
+// see content/js/.eslintrc and modules/.eslintrc
 {
   "plugins": [
     "react"
   ],
   "ecmaFeatures": {
     "forOf": true,
     "jsx": true,
-    // These are on for this directory for .jsm and content/js files.
-    // If adding more items here, consider turning them off for the following
-    // files if they aren't supported by all browsers.
-    // content/shared/.eslintrc
-    // content/standalone/.eslintrc
-    "blockBindings": true,
-    "arrowFunctions": true,
-    "destructuring": true,
-    "generators": true,
-    "spread": true,
-    "restParams": true,
-    "objectLiteralShorthandMethods": true
   },
   "env": {
     "browser": true,
     "mocha": true
   },
   "globals": {
     "_": false,
     "$": false,
@@ -38,69 +28,71 @@
   },
   "rules": {
     // turn off all kinds of stuff that we actually do want, because
     // right now, we're bootstrapping the linting infrastructure.  We'll
     // want to audit these rules, and start turning them on and fixing the
     // problems they find, one at a time.
 
     // Eslint built-in rules are documented at <http://eslint.org/docs/rules/>
-    "camelcase": 0,
-    "comma-dangle": 0,
-    "comma-spacing": 0,
-    "consistent-return": 0,
-    "curly": 0,
-    "dot-notation": 0,
-    "eol-last": 0,
-    "eqeqeq": 0,
-    "global-strict": 0,
-    "key-spacing": 0,
-    "new-cap": 0,
-    "no-catch-shadow": 0,
-    "no-console": 0,
-    "no-empty": 0,
-    "no-extra-bind": 0,
-    "no-extra-boolean-cast": 0,
-    "no-extra-semi": 0,
-    "no-multi-spaces": 0,
-    "no-new": 0,
-    "no-redeclare": 0,
-    "no-return-assign": 0,
-    "no-shadow": 0,
-    "no-spaced-func": 0,
-    "no-trailing-spaces": 0,
-    "no-undef": 0,
-    "no-underscore-dangle": 0,
-    "no-unused-expressions": 0,
-    "no-unused-vars": 0,
-    "no-use-before-define": 0,
-    "no-wrap-func": 0,
-    "quotes": 0,
-    "semi": 0,
-    "semi-spacing": 0,
-    "space-infix-ops": 0,
-    "space-return-throw-case": 0,
-    "strict": 0,
-    "yoda": 0,
+    "camelcase": 0,               // TODO: Remove (use default)
+    "comma-dangle": 0,            // TODO: Remove (use default)
+    "comma-spacing": 0,           // TODO: Remove (use default)
+    "consistent-return": 0,       // TODO: Remove (use default)
+    "curly": 0,                   // TODO: Remove (use default)
+    "dot-notation": 0,            // TODO: Remove (use default)
+    "eol-last": 0,                // TODO: Remove (use default)
+    "eqeqeq": 0,                  // TBD. Might need to be separate for content & chrome
+    "global-strict": 0,           // Leave as zero (this will be unsupported in eslint 1.0.0)
+    "key-spacing": 0,             // TODO: Remove (use default)
+    "new-cap": 0,                 // TODO: Remove (use default)
+    "no-catch-shadow": 0,         // TODO: Remove (use default)
+    "no-console": 0,              // Leave as 0. We use console logging in content code.
+    "no-empty": 0,                // TODO: Remove (use default)
+    "no-extra-bind": 0,           // Leave as 0
+    "no-extra-boolean-cast": 0,   // TODO: Remove (use default)
+    "no-extra-semi": 0,           // TODO: Remove (use default)
+    "no-multi-spaces": 0,         // TBD.
+    "no-new": 0,                  // TODO: Remove (use default)
+    "no-redeclare": 0,            // TODO: Remove (use default)
+    "no-return-assign": 0,        // TODO: Remove (use default)
+    "no-shadow": 0,               // TODO: Remove (use default)
+    "no-spaced-func": 0,          // TODO: Remove (use default)
+    "no-trailing-spaces": 0,      // TODO: Remove (use default)
+    "no-undef": 0,                // TODO: Remove (use default)
+    "no-underscore-dangle": 0,    // Leave as 0. Commonly used for private variables.
+    "no-unused-expressions": 0,   // TODO: Remove (use default)
+    "no-unused-vars": 0,          // TODO: Remove (use default)
+    "no-use-before-define": 0,    // TODO: Remove (use default)
+    "no-wrap-func": 0,            // TODO: Remove (use default)
+    "quotes": 0,                  // [2, "double", "avoid-escape"],
+    "semi": 0,                    // TODO: Remove (use default)
+    "semi-spacing": 0,            // TODO: Remove (use default)
+    "space-infix-ops": 0,         // TODO: Remove (use default)
+    "space-return-throw-case": 0, // TODO: Remove (use default)
+    "strict": 0,                  // [2, "function"],
+    "yoda": 0,                    // [2, "never"],
     // eslint-plugin-react rules. These are documented at
     // <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
     "react/jsx-quotes": [2, "double", "avoid-escape"],
     "react/jsx-no-undef": 2,
     // Need to fix instances where this is failing.
     "react/jsx-sort-props": 0,
+    "react/jsx-sort-prop-types": 0,
     "react/jsx-uses-vars": 2,
     // Need to fix the couple of instances which don't
     // currently pass this rule.
     "react/no-did-mount-set-state": 0,
     "react/no-did-update-set-state": 2,
     "react/no-unknown-property": 2,
     // Need to fix instances where this is currently failing
     "react/prop-types": 0,
     "react/self-closing-comp": 2,
     "react/wrap-multilines": 2,
     // Not worth it: React is defined globally
     "react/jsx-uses-react": 0,
     "react/react-in-jsx-scope": 0,
     // These ones we don't want to ever enable
     "react/display-name": 0,
+    "react/jsx-boolean-value": 0,
     "react/no-multi-comp": 0
   }
 }
--- a/browser/components/loop/README.txt
+++ b/browser/components/loop/README.txt
@@ -49,17 +49,17 @@ you've installed the dependencies by typ
 
 If you install eslint and the react plugin globally:
 
   npm install -g eslint
   npm install -g eslint-plugin-react
 
 You can also run it by hand in the browser/components/loop directory:
 
-  eslint -ext .js -ext .jsx --ext .jsm .
+  eslint --ext .js --ext .jsx --ext .jsm .
 
 Front-End Unit Tests
 ====================
 The unit tests for Loop reside in three directories:
 
 - test/desktop-local
 - test/shared
 - test/standalone
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/js/.eslintrc
@@ -0,0 +1,15 @@
+{
+  "ecmaFeatures": {
+   // These are on for this directory for .jsm and content/js files.
+    "blockBindings": true,
+    "arrowFunctions": true,
+    "destructuring": true,
+    "generators": true,
+    "spread": true,
+    "restParams": true,
+    "objectLiteralShorthandMethods": true
+  },
+  "rules": {
+    "generator-star-spacing": [2, "after"]
+  }
+}
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -217,17 +217,17 @@ loop.contacts = (function(_, mozL10n) {
               onClick: this.onItemClick, "data-action": blockAction}, 
             React.createElement("i", {className: "icon icon-" + blockAction}), 
             mozL10n.get(blockLabel)
           ), 
           React.createElement("li", {className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
               onClick: this.onItemClick, "data-action": "remove"}, 
             React.createElement("i", {className: "icon icon-remove"}), 
-            mozL10n.get("remove_contact_menu_button")
+            mozL10n.get("remove_contact_menu_button2")
           )
         )
       );
     }
   });
 
   const ContactDetail = React.createClass({displayName: "ContactDetail",
     getInitialState: function() {
@@ -582,17 +582,17 @@ loop.contacts = (function(_, mozL10n) {
       }
 
       return (
         React.createElement("div", null, 
           React.createElement("div", {className: "content-area"}, 
             React.createElement(ButtonGroup, null, 
               React.createElement(Button, {caption: this.state.importBusy
                                ? mozL10n.get("importing_contacts_progress_button")
-                               : mozL10n.get("import_contacts_button"), 
+                               : mozL10n.get("import_contacts_button2"), 
                       disabled: this.state.importBusy, 
                       onClick: this.handleImportButtonClick}, 
                 React.createElement("div", {className: cx({"contact-import-spinner": true,
                                     spinner: true,
                                     busy: this.state.importBusy})})
               ), 
               React.createElement(Button, {caption: mozL10n.get("new_contact_button"), 
                       onClick: this.handleAddContactButtonClick})
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -217,17 +217,17 @@ loop.contacts = (function(_, mozL10n) {
               onClick={this.onItemClick} data-action={blockAction}>
             <i className={"icon icon-" + blockAction} />
             {mozL10n.get(blockLabel)}
           </li>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
               onClick={this.onItemClick} data-action="remove">
             <i className="icon icon-remove" />
-            {mozL10n.get("remove_contact_menu_button")}
+            {mozL10n.get("remove_contact_menu_button2")}
           </li>
         </ul>
       );
     }
   });
 
   const ContactDetail = React.createClass({
     getInitialState: function() {
@@ -582,17 +582,17 @@ loop.contacts = (function(_, mozL10n) {
       }
 
       return (
         <div>
           <div className="content-area">
             <ButtonGroup>
               <Button caption={this.state.importBusy
                                ? mozL10n.get("importing_contacts_progress_button")
-                               : mozL10n.get("import_contacts_button")}
+                               : mozL10n.get("import_contacts_button2")}
                       disabled={this.state.importBusy}
                       onClick={this.handleImportButtonClick}>
                 <div className={cx({"contact-import-spinner": true,
                                     spinner: true,
                                     busy: this.state.importBusy})} />
               </Button>
               <Button caption={mozL10n.get("new_contact_button")}
                       onClick={this.handleAddContactButtonClick} />
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -6,20 +6,21 @@
 
 /* jshint newcap:false */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
+  var ROOM_STATES = loop.store.ROOM_STATES;
+  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
-  var ROOM_STATES = loop.store.ROOM_STATES;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
+  var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
 
   /**
    * ActiveRoomStore mixin.
    * @type {Object}
    */
   var ActiveRoomStoreMixin = {
     mixins: [Backbone.Events],
@@ -307,23 +308,35 @@ loop.roomViews = (function(mozL10n) {
     render: function() {
       if (!this.state.show)
         return null;
 
       var URL = this.props.roomData.roomContextUrls && this.props.roomData.roomContextUrls[0];
       var thumbnail = URL && URL.thumbnail || "";
       var URLDescription = URL && URL.description || "";
       var location = URL && URL.location || "";
+      var locationData = null;
+      if (location) {
+        locationData = sharedUtils.formatURL(location);
+      }
+
+      if (!locationData) {
+        return null;
+      }
+
       return (
         React.createElement("div", {className: "room-context"}, 
           React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}), 
           React.createElement("div", {className: "room-context-content"}, 
             React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")), 
             React.createElement("div", {className: "room-context-description"}, URLDescription), 
-            React.createElement("a", {className: "room-context-url", href: location, target: "_blank"}, location), 
+            React.createElement("a", {className: "room-context-url", 
+               href: location, 
+               target: "_blank", 
+               title: locationData.location}, locationData.hostname), 
             this.props.roomData.roomDescription ?
               React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) :
               null, 
             React.createElement("button", {className: "room-context-btn-close", 
                     onClick: this.handleCloseClick})
           )
         )
       );
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -6,20 +6,21 @@
 
 /* jshint newcap:false */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
+  var ROOM_STATES = loop.store.ROOM_STATES;
+  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
-  var ROOM_STATES = loop.store.ROOM_STATES;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
+  var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
 
   /**
    * ActiveRoomStore mixin.
    * @type {Object}
    */
   var ActiveRoomStoreMixin = {
     mixins: [Backbone.Events],
@@ -307,23 +308,35 @@ loop.roomViews = (function(mozL10n) {
     render: function() {
       if (!this.state.show)
         return null;
 
       var URL = this.props.roomData.roomContextUrls && this.props.roomData.roomContextUrls[0];
       var thumbnail = URL && URL.thumbnail || "";
       var URLDescription = URL && URL.description || "";
       var location = URL && URL.location || "";
+      var locationData = null;
+      if (location) {
+        locationData = sharedUtils.formatURL(location);
+      }
+
+      if (!locationData) {
+        return null;
+      }
+
       return (
         <div className="room-context">
           <img className="room-context-thumbnail" src={thumbnail}/>
           <div className="room-context-content">
             <div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
             <div className="room-context-description">{URLDescription}</div>
-            <a className="room-context-url" href={location} target="_blank">{location}</a>
+            <a className="room-context-url"
+               href={location}
+               target="_blank"
+               title={locationData.location}>{locationData.hostname}</a>
             {this.props.roomData.roomDescription ?
               <div className="room-context-comment">{this.props.roomData.roomDescription}</div> :
               null}
             <button className="room-context-btn-close"
                     onClick={this.handleCloseClick}/>
           </div>
         </div>
       );
deleted file mode 100644
--- a/browser/components/loop/content/shared/.eslintrc
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "ecmaFeatures": {
-    // Turn off top-level items needed for the jsm files, but not wanted
-    // for shared code as we can't support them.
-    "blockBindings": false,
-    "arrowFunctions": false,
-    "destructuring": false,
-    "forOf": true,
-    "generators": false,
-    "spread": false,
-    "restParams": false,
-    "objectLiteralShorthandMethods": false
-  }
-}
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -784,17 +784,17 @@ loop.OTSdkDriver = (function() {
       if (callLengthSeconds >= 10 && callLengthSeconds <= 30) {
         bucket = buckets.BETWEEN_10S_AND_30S;
       } else if (callLengthSeconds > 30 && callLengthSeconds <= 300) {
         bucket = buckets.BETWEEN_30S_AND_5M;
       } else if (callLengthSeconds > 300) {
         bucket = buckets.MORE_THAN_5M;
       }
 
-      this.mozLoop.telemetryAddValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH", bucket);
+      this.mozLoop.telemetryAddValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", bucket);
       this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_ALREADY_NOTED);
 
       this._connectionLengthNotedCalls++;
       if (this._debugTwoWayMediaTelemetry) {
         console.log('Loop Telemetry: noted two-way media connection ' +
           'in bucket: ', bucket);
       }
     },
@@ -852,15 +852,15 @@ loop.OTSdkDriver = (function() {
 
       var bucket = this.mozLoop.SHARING_STATE_CHANGE[type.toUpperCase() + "_" +
         (enabled ? "ENABLED" : "DISABLED")];
       if (!bucket) {
         console.error("No sharing state bucket found for '" + type + "'");
         return;
       }
 
-      this.mozLoop.telemetryAddValue("LOOP_SHARING_STATE_CHANGE", bucket);
+      this.mozLoop.telemetryAddValue("LOOP_SHARING_STATE_CHANGE_1", bucket);
     }
   };
 
   return OTSdkDriver;
 
 })();
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -267,16 +267,43 @@ var inChrome = typeof Components != "und
   function locationData() {
     return {
       hash: window.location.hash,
       pathname: window.location.pathname
     };
   }
 
   /**
+   * Formats a url for display purposes. This includes converting the
+   * domain to punycode, and then decoding the url.
+   *
+   * @param {String} url The url to format.
+   * @return {Object}    An object containing the hostname and full location.
+   */
+  function formatURL(url) {
+    // We're using new URL to pass this through the browser's ACE/punycode
+    // processing system. If the browser considers a url to need to be
+    // punycode encoded for it to be displayed, then new URL will do that for
+    // us. This saves us needing our own punycode library.
+    var urlObject;
+    try {
+      urlObject = new URL(url);
+    } catch (ex) {
+      console.error("Error occurred whilst parsing URL:", ex);
+      return null;
+    }
+
+    // Finally, ensure we look good.
+    return {
+      hostname: urlObject.hostname,
+      location: decodeURI(urlObject.href)
+    };
+  }
+
+  /**
    * Generates and opens a mailto: url with call URL information prefilled.
    * Note: This only works for Desktop.
    *
    * @param  {String} callUrl   The call URL.
    * @param  {String} recipient The recipient email address (optional).
    */
   function composeCallUrlEmail(callUrl, recipient) {
     if (typeof navigator.mozLoop === "undefined") {
@@ -531,16 +558,17 @@ var inChrome = typeof Components != "und
     FAILURE_DETAILS: FAILURE_DETAILS,
     REST_ERRNOS: REST_ERRNOS,
     WEBSOCKET_REASONS: WEBSOCKET_REASONS,
     STREAM_PROPERTIES: STREAM_PROPERTIES,
     SCREEN_SHARE_STATES: SCREEN_SHARE_STATES,
     ROOM_INFO_FAILURES: ROOM_INFO_FAILURES,
     composeCallUrlEmail: composeCallUrlEmail,
     formatDate: formatDate,
+    formatURL: formatURL,
     getBoolPreference: getBoolPreference,
     getOS: getOS,
     getOSVersion: getOSVersion,
     isChrome: isChrome,
     isFirefox: isFirefox,
     isFirefoxOS: isFirefoxOS,
     isOpera: isOpera,
     getUnsupportedPlatform: getUnsupportedPlatform,
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/modules/.eslintrc
@@ -0,0 +1,17 @@
+{
+  "ecmaFeatures": {
+    "arrowFunctions": true,
+    "blockBindings": true,
+    "destructuring": true,
+    "generators": true,
+    "restParams": true,
+    "spread": true,
+    "objectLiteralShorthandMethods": true,
+  },
+  "rules": {
+    "generator-star-spacing": [2, "after"],
+    // We should fix the errors and enable this (set to 2)
+    "no-var": 0,
+    "strict": [2, "global"]
+  }
+}
rename from browser/components/loop/CardDavImporter.jsm
rename to browser/components/loop/modules/CardDavImporter.jsm
rename from browser/components/loop/GoogleImporter.jsm
rename to browser/components/loop/modules/GoogleImporter.jsm
rename from browser/components/loop/LoopCalls.jsm
rename to browser/components/loop/modules/LoopCalls.jsm
rename from browser/components/loop/LoopContacts.jsm
rename to browser/components/loop/modules/LoopContacts.jsm
rename from browser/components/loop/LoopRooms.jsm
rename to browser/components/loop/modules/LoopRooms.jsm
rename from browser/components/loop/LoopStorage.jsm
rename to browser/components/loop/modules/LoopStorage.jsm
rename from browser/components/loop/MozLoopAPI.jsm
rename to browser/components/loop/modules/MozLoopAPI.jsm
rename from browser/components/loop/MozLoopPushHandler.jsm
rename to browser/components/loop/modules/MozLoopPushHandler.jsm
rename from browser/components/loop/MozLoopService.jsm
rename to browser/components/loop/modules/MozLoopService.jsm
rename from browser/components/loop/MozLoopWorker.js
rename to browser/components/loop/modules/MozLoopWorker.js
--- a/browser/components/loop/moz.build
+++ b/browser/components/loop/moz.build
@@ -8,24 +8,24 @@ JAR_MANIFESTS += ['jar.mn']
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
 BROWSER_CHROME_MANIFESTS += [
     'test/mochitest/browser.ini',
 ]
 
 EXTRA_JS_MODULES.loop += [
-    'CardDavImporter.jsm',
     'content/shared/js/crypto.js',
     'content/shared/js/utils.js',
-    'GoogleImporter.jsm',
-    'LoopCalls.jsm',
-    'LoopContacts.jsm',
-    'LoopRooms.jsm',
-    'LoopStorage.jsm',
-    'MozLoopAPI.jsm',
-    'MozLoopPushHandler.jsm',
-    'MozLoopService.jsm',
-    'MozLoopWorker.js',
+    'modules/CardDavImporter.jsm',
+    'modules/GoogleImporter.jsm',
+    'modules/LoopCalls.jsm',
+    'modules/LoopContacts.jsm',
+    'modules/LoopRooms.jsm',
+    'modules/LoopStorage.jsm',
+    'modules/MozLoopAPI.jsm',
+    'modules/MozLoopPushHandler.jsm',
+    'modules/MozLoopService.jsm',
+    'modules/MozLoopWorker.js',
 ]
 
 with Files('**'):
     BUG_COMPONENT = ('Loop', 'Client')
deleted file mode 100644
--- a/browser/components/loop/standalone/.eslintrc
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "ecmaFeatures": {
-    // Turn off top-level items needed for the jsm files, but not wanted
-    // for shared code as we can't support them.
-    "blockBindings": false,
-    "arrowFunctions": false,
-    "destructuring": false,
-    "forOf": true,
-    "generators": false,
-    "spread": false,
-    "restParams": false,
-    "objectLiteralShorthandMethods": false
-  }
-}
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -11,16 +11,17 @@ var loop = loop || {};
 loop.standaloneRoomViews = (function(mozL10n) {
   "use strict";
 
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
+  var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({displayName: "StandaloneRoomInfoArea",
     propTypes: {
       isFirefox: React.PropTypes.bool.isRequired,
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
@@ -243,33 +244,37 @@ loop.standaloneRoomViews = (function(moz
     },
 
     render: function() {
       if (!this.props.roomContextUrl ||
           !this.props.roomContextUrl.location) {
         return null;
       }
 
-      var location = this.props.roomContextUrl.location;
+      var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
+      if (!locationInfo) {
+        return null;
+      }
 
       var cx = React.addons.classSet;
 
       var classes = cx({
         "standalone-context-url": true,
         "screen-share-active": this.props.receivingScreenShare
       });
 
       return (
         React.createElement("div", {className: classes}, 
             React.createElement("img", {src: this.props.roomContextUrl.thumbnail}), 
           React.createElement("div", {className: "standalone-context-url-description-wrapper"}, 
             this.props.roomContextUrl.description, 
-            React.createElement("br", null), React.createElement("a", {href: location, 
+            React.createElement("br", null), React.createElement("a", {href: locationInfo.location, 
                      onClick: this.recordClick, 
-                     target: "_blank"}, location)
+                     target: "_blank", 
+                     title: locationInfo.location}, locationInfo.hostname)
           )
         )
       );
     }
   });
 
   var StandaloneRoomContextView = React.createClass({displayName: "StandaloneRoomContextView",
     propTypes: {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -11,16 +11,17 @@ var loop = loop || {};
 loop.standaloneRoomViews = (function(mozL10n) {
   "use strict";
 
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
+  var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({
     propTypes: {
       isFirefox: React.PropTypes.bool.isRequired,
       activeRoomStore: React.PropTypes.oneOfType([
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore),
         React.PropTypes.instanceOf(loop.store.FxOSActiveRoomStore)
@@ -243,33 +244,37 @@ loop.standaloneRoomViews = (function(moz
     },
 
     render: function() {
       if (!this.props.roomContextUrl ||
           !this.props.roomContextUrl.location) {
         return null;
       }
 
-      var location = this.props.roomContextUrl.location;
+      var locationInfo = sharedUtils.formatURL(this.props.roomContextUrl.location);
+      if (!locationInfo) {
+        return null;
+      }
 
       var cx = React.addons.classSet;
 
       var classes = cx({
         "standalone-context-url": true,
         "screen-share-active": this.props.receivingScreenShare
       });
 
       return (
         <div className={classes}>
             <img src={this.props.roomContextUrl.thumbnail} />
           <div className="standalone-context-url-description-wrapper">
             {this.props.roomContextUrl.description}
-            <br /><a href={location}
+            <br /><a href={locationInfo.location}
                      onClick={this.recordClick}
-                     target="_blank">{location}</a>
+                     target="_blank"
+                     title={locationInfo.location}>{locationInfo.hostname}</a>
           </div>
         </div>
       );
     }
   });
 
   var StandaloneRoomContextView = React.createClass({
     propTypes: {
--- a/browser/components/loop/standalone/package.json
+++ b/browser/components/loop/standalone/package.json
@@ -7,18 +7,18 @@
     "url": "git@github.com:mozilla/loop-client.git"
   },
   "engines": {
     "node": "0.10.x",
     "npm": "1.3.x"
   },
   "dependencies": {},
   "devDependencies": {
-    "eslint": "0.18.x",
-    "eslint-plugin-react": "2.0.x",
+    "eslint": "0.20.x",
+    "eslint-plugin-react": "2.2.x",
     "express": "3.x"
   },
   "scripts": {
     "test": "make test",
     "start": "make runserver"
   },
   "license": "MPL-2.0"
 }
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/.eslintrc
@@ -0,0 +1,7 @@
+{
+  "rules": {
+    // This is useful for some of the tests, e.g.
+    // expect(new Foo()).to.Throw(/error/)
+    "no-new": 0
+  }
+}
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -237,16 +237,33 @@ describe("loop.roomViews", function () {
           showContext: true,
           roomData: {
             roomContextUrls: [fakeContextURL]
           }
         });
 
         expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
       });
+
+      it("should format the context url for display", function() {
+        sandbox.stub(sharedUtils, "formatURL").returns({
+          location: "location",
+          hostname: "hostname"
+        });
+
+        view = mountTestComponent({
+          showContext: true,
+          roomData: {
+            roomContextUrls: [fakeContextURL]
+          }
+        });
+
+        expect(view.getDOMNode().querySelector(".room-context-url").textContent)
+          .eql("hostname");
+      });
     });
   });
 
   describe("DesktopRoomConversationView", function() {
     var view;
 
     beforeEach(function() {
       loop.store.StoreMixin.register({
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/.eslintrc
@@ -0,0 +1,17 @@
+{
+  "ecmaFeatures": {
+    "arrowFunctions": true,
+    "blockBindings": true,
+    "destructuring": true,
+    "generators": true,
+    "restParams": true,
+    "spread": true,
+    "objectLiteralShorthandMethods": true,
+  },
+  "rules": {
+    "generator-star-spacing": [2, "after"],
+    // We should fix the errors and enable this (set to 2)
+    "no-var": 0,
+    "strict": [2, "global"]
+  }
+}
--- a/browser/components/loop/test/mochitest/browser_CardDavImporter.js
+++ b/browser/components/loop/test/mochitest/browser_CardDavImporter.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
 
 const kAuth = {
   "method": "basic",
   "user": "username",
   "password": "p455w0rd"
 }
 
--- a/browser/components/loop/test/mochitest/browser_GoogleImporter.js
+++ b/browser/components/loop/test/mochitest/browser_GoogleImporter.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const {GoogleImporter} = Cu.import("resource:///modules/loop/GoogleImporter.jsm", {});
 
 let importer = new GoogleImporter();
 
 function promiseImport() {
   return new Promise(function(resolve, reject) {
     importer.startImport({}, function(err, stats) {
       if (err) {
--- a/browser/components/loop/test/mochitest/browser_LoopContacts.js
+++ b/browser/components/loop/test/mochitest/browser_LoopContacts.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const {LoopContacts} = Cu.import("resource:///modules/loop/LoopContacts.jsm", {});
 const {LoopStorage} = Cu.import("resource:///modules/loop/LoopStorage.jsm", {});
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 const kContacts = [{
@@ -216,18 +218,23 @@ add_task(function* () {
 
   info("Get a couple of contacts.");
   yield new Promise((resolve, reject) => {
     let toRetrieve = [contacts[0], contacts[2], contacts[3]];
     LoopContacts.getMany(toRetrieve.map(contact => contact._guid), (err, result) => {
       Assert.ok(!err, "There shouldn't be an error");
       Assert.equal(result.length, toRetrieve.length, "Result list should be the same " +
                    "size as the list of items to retrieve");
+
+      function resultFilter(c) {
+        return c._guid == this._guid;
+      }
+
       for (let contact of toRetrieve) {
-        let found = result.filter(c => c._guid == contact._guid);
+        let found = result.filter(resultFilter.bind(contact));
         Assert.ok(found.length, "Contact " + contact._guid + " should be in the list");
         compareContacts(found[0], contact);
       }
       resolve();
     });
   });
 
   info("Get all contacts.");
--- a/browser/components/loop/test/mochitest/browser_mozLoop_appVersionInfo.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_appVersionInfo.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * This is an integration test from navigator.mozLoop through to the end
  * effects - rather than just testing MozLoopAPI alone.
  */
 
+"use strict";
+
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
 add_task(loadLoopPanel);
 
 add_task(function* test_mozLoop_appVersionInfo() {
   Assert.ok(gMozLoopAPI, "mozLoop should exist");
 
   let appVersionInfo = gMozLoopAPI.appVersionInfo;
--- a/browser/components/loop/test/mochitest/browser_mozLoop_doNotDisturb.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_doNotDisturb.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * This is an integration test from navigator.mozLoop through to the end
  * effects - rather than just testing MozLoopAPI alone.
  */
 
+"use strict";
+
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
 add_task(loadLoopPanel);
 
 add_task(function* test_mozLoop_doNotDisturb() {
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("loop.do_not_disturb");
   });
--- a/browser/components/loop/test/mochitest/browser_mozLoop_pluralStrings.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_pluralStrings.js
@@ -1,21 +1,23 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This is an integration test from navigator.mozLoop through to the end
- * effects - rather than just testing MozLoopAPI alone.
- */
-
-Components.utils.import("resource://gre/modules/Promise.jsm", this);
-
-add_task(loadLoopPanel);
-
-add_task(function* test_mozLoop_pluralStrings() {
-  Assert.ok(gMozLoopAPI, "mozLoop should exist");
-
-  var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in2"));
-  Assert.equal(gMozLoopAPI.getPluralForm(0, strings.textContent),
-               "This window will close in {{countdown}} seconds");
-  Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
-               "This window will close in {{countdown}} second");
-});
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This is an integration test from navigator.mozLoop through to the end
+ * effects - rather than just testing MozLoopAPI alone.
+ */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/Promise.jsm", this);
+
+add_task(loadLoopPanel);
+
+add_task(function* test_mozLoop_pluralStrings() {
+  Assert.ok(gMozLoopAPI, "mozLoop should exist");
+
+  var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in2"));
+  Assert.equal(gMozLoopAPI.getPluralForm(0, strings.textContent),
+               "This window will close in {{countdown}} seconds");
+  Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
+               "This window will close in {{countdown}} second");
+});
--- a/browser/components/loop/test/mochitest/browser_mozLoop_prefs.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_prefs.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * This is an integration test from navigator.mozLoop through to the end
  * effects - rather than just testing MozLoopAPI alone.
  */
 
+"use strict";
+
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
 add_task(loadLoopPanel);
 
 add_task(function* test_mozLoop_charPref() {
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("loop.test");
   });
--- a/browser/components/loop/test/mochitest/browser_mozLoop_socialShare.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_socialShare.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * This is an integration test from navigator.mozLoop through to the end
  * effects - rather than just testing MozLoopAPI alone.
  */
 
+"use strict";
+
 Cu.import("resource://gre/modules/Promise.jsm");
 const {SocialService} = Cu.import("resource://gre/modules/SocialService.jsm", {});
 
 add_task(loadLoopPanel);
 
 const kShareWidgetId = "social-share-button";
 const kShareProvider = {
   name: "provider 1",
--- a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
@@ -1,14 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /*
  * This file contains tests for the mozLoop telemetry API.
  */
+
+"use strict";
+
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
 add_task(loadLoopPanel);
 
 /**
  * Enable local telemetry recording for the duration of the tests.
  */
 add_task(function* test_initialize() {
@@ -18,17 +21,17 @@ add_task(function* test_initialize() {
     Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 /**
  * Tests that enumerated bucket histograms exist and can be updated.
  */
 add_task(function* test_mozLoop_telemetryAdd_buckets() {
-  let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH";
+  let histogramId = "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1";
   let histogram = Services.telemetry.getHistogramById(histogramId);
   let CONN_LENGTH = gMozLoopAPI.TWO_WAY_MEDIA_CONN_LENGTH;
 
   histogram.clear();
   for (let value of [CONN_LENGTH.SHORTER_THAN_10S,
                      CONN_LENGTH.BETWEEN_10S_AND_30S,
                      CONN_LENGTH.BETWEEN_10S_AND_30S,
                      CONN_LENGTH.BETWEEN_30S_AND_5M,
@@ -44,17 +47,17 @@ add_task(function* test_mozLoop_telemetr
   let snapshot = histogram.snapshot();
   is(snapshot.counts[CONN_LENGTH.SHORTER_THAN_10S], 1, "TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S");
   is(snapshot.counts[CONN_LENGTH.BETWEEN_10S_AND_30S], 2, "TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S");
   is(snapshot.counts[CONN_LENGTH.BETWEEN_30S_AND_5M], 3, "TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M");
   is(snapshot.counts[CONN_LENGTH.MORE_THAN_5M], 4, "TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M");
 });
 
 add_task(function* test_mozLoop_telemetryAdd_sharing_buckets() {
-  let histogramId = "LOOP_SHARING_STATE_CHANGE";
+  let histogramId = "LOOP_SHARING_STATE_CHANGE_1";
   let histogram = Services.telemetry.getHistogramById(histogramId);
   const SHARING_STATES = gMozLoopAPI.SHARING_STATE_CHANGE;
 
   histogram.clear();
   for (let value of [SHARING_STATES.WINDOW_ENABLED,
                      SHARING_STATES.WINDOW_DISABLED,
                      SHARING_STATES.WINDOW_DISABLED,
                      SHARING_STATES.BROWSER_ENABLED,
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const HAWK_TOKEN_LENGTH = 64;
 const {
   LOOP_SESSION_TYPE,
   MozLoopServiceInternal,
   MozLoopService,
 } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 const {LoopCalls} = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
 const {LoopRooms} = Cu.import("resource:///modules/loop/LoopRooms.jsm", {});
@@ -118,18 +120,18 @@ function loadLoopPanel(aOverrideOptions 
   registerCleanupFunction(function() {
     Services.io.offline = WAS_OFFLINE;
   });
 
   // Turn off animations to make tests quicker.
   let loopPanel = document.getElementById("loop-notification-panel");
   loopPanel.setAttribute("animate", "false");
 
-  // Now get the actual API.
-  yield promiseGetMozLoopAPI();
+  // Now get the actual API loaded into gMozLoopAPI.
+  return promiseGetMozLoopAPI();
 }
 
 function promiseOAuthParamsSetup(baseURL, params) {
   return new Promise((resolve, reject) => {
     let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                 createInstance(Ci.nsIXMLHttpRequest);
     xhr.open("POST", baseURL + "/setup_params", true);
     xhr.setRequestHeader("X-Params", JSON.stringify(params));
@@ -314,17 +316,17 @@ const mockDb = {
       callback(new Error("No 'id' field present"));
       return;
     }
     details._guid = this._next_guid++;
     this._store[details._guid] = details;
     callback(null, details);
   },
   remove: function(guid, callback) {
-    if (!guid in this._store) {
+    if (!(guid in this._store)) {
       callback(new Error("Could not find _guid '" + guid + "' in database"));
       return;
     }
     delete this._store[guid];
     callback(null);
   },
   getAll: function(callback) {
     callback(null, this._store);
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -468,52 +468,52 @@ describe("loop.OTSdkDriver", function ()
 
     it("should call mozLoop.noteConnectionLength with SHORTER_THAN_10S for calls less than 10s", function() {
       var endTimeMS = 9000;
 
       driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
 
       sinon.assert.calledOnce(mozLoop.telemetryAddValue);
       sinon.assert.calledWith(mozLoop.telemetryAddValue,
-        "LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
+        "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
         mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S);
     });
 
     it("should call mozLoop.noteConnectionLength with BETWEEN_10S_AND_30S for 15s calls",
       function() {
         var endTimeMS = 15000;
 
         driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
 
         sinon.assert.calledOnce(mozLoop.telemetryAddValue);
         sinon.assert.calledWith(mozLoop.telemetryAddValue,
-          "LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
+          "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
           mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S);
       });
 
     it("should call mozLoop.noteConnectionLength with BETWEEN_30S_AND_5M for 60s calls",
       function() {
         var endTimeMS = 60 * 1000;
 
         driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
 
         sinon.assert.calledOnce(mozLoop.telemetryAddValue);
         sinon.assert.calledWith(mozLoop.telemetryAddValue,
-          "LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
+          "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
           mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M);
       });
 
     it("should call mozLoop.noteConnectionLength with MORE_THAN_5M for 10m calls", function() {
       var endTimeMS = 10 * 60 * 1000;
 
       driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
 
       sinon.assert.calledOnce(mozLoop.telemetryAddValue);
       sinon.assert.calledWith(mozLoop.telemetryAddValue,
-        "LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
+        "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1",
         mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M);
     });
 
     it("should not call mozLoop.noteConnectionLength if" +
        " driver._sendTwoWayMediaTelemetry is false",
       function() {
         var endTimeMS = 10 * 60 * 1000;
         driver._sendTwoWayMediaTelemetry = false;
@@ -525,44 +525,44 @@ describe("loop.OTSdkDriver", function ()
   });
 
   describe("#_noteSharingState", function() {
     it("should record enabled sharing states for window", function() {
       driver._noteSharingState("window", true);
 
       sinon.assert.calledOnce(mozLoop.telemetryAddValue);
       sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
-        "LOOP_SHARING_STATE_CHANGE",
+        "LOOP_SHARING_STATE_CHANGE_1",
         mozLoop.SHARING_STATE_CHANGE.WINDOW_ENABLED);
     });
 
     it("should record enabled sharing states for browser", function() {
       driver._noteSharingState("browser", true);
 
       sinon.assert.calledOnce(mozLoop.telemetryAddValue);
       sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
-        "LOOP_SHARING_STATE_CHANGE",
+        "LOOP_SHARING_STATE_CHANGE_1",
         mozLoop.SHARING_STATE_CHANGE.BROWSER_ENABLED);
     });
 
     it("should record disabled sharing states for window", function() {
       driver._noteSharingState("window", false);
 
       sinon.assert.calledOnce(mozLoop.telemetryAddValue);
       sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
-        "LOOP_SHARING_STATE_CHANGE",
+        "LOOP_SHARING_STATE_CHANGE_1",
         mozLoop.SHARING_STATE_CHANGE.WINDOW_DISABLED);
     });
 
     it("should record disabled sharing states for browser", function() {
       driver._noteSharingState("browser", false);
 
       sinon.assert.calledOnce(mozLoop.telemetryAddValue);
       sinon.assert.calledWithExactly(mozLoop.telemetryAddValue,
-        "LOOP_SHARING_STATE_CHANGE",
+        "LOOP_SHARING_STATE_CHANGE_1",
         mozLoop.SHARING_STATE_CHANGE.BROWSER_DISABLED);
     });
   });
 
   describe("#forceDisconnectAll", function() {
     it("should not disconnect anything when not connected", function() {
       driver.session = session;
       driver.forceDisconnectAll(function() {});
--- a/browser/components/loop/test/shared/utils_test.js
+++ b/browser/components/loop/test/shared/utils_test.js
@@ -140,16 +140,41 @@ describe("loop.shared.utils", function()
       it("should return the localStorage value", function() {
         localStorage.setItem("test.true", true);
 
         expect(sharedUtils.getBoolPreference("test.true")).eql(true);
       });
     });
   });
 
+  describe("#formatURL", function() {
+    it("should decode encoded URIs", function() {
+      expect(sharedUtils.formatURL("http://invalid.com/?a=Foo%20Bar"))
+        .eql({
+          location: "http://invalid.com/?a=Foo Bar",
+          hostname: "invalid.com"
+        });
+    });
+
+    it("should change some idn urls to ascii encoded", function() {
+      // Note, this is based on the browser's list of what does/doesn't get
+      // altered for punycode, so if the list changes this could change in the
+      // future.
+      expect(sharedUtils.formatURL("http://\u0261oogle.com/"))
+        .eql({
+          location: "http://xn--oogle-qmc.com/",
+          hostname: "xn--oogle-qmc.com"
+        });
+    });
+
+    it("should return null if it the url is not valid", function() {
+      expect(sharedUtils.formatURL("hinvalid//url")).eql(null);
+    });
+  });
+
   describe("#composeCallUrlEmail", function() {
     var composeEmail;
 
     beforeEach(function() {
       // fake mozL10n
       sandbox.stub(navigator.mozL10n, "get", function(id) {
         switch(id) {
           case "share_email_subject5": return "subject";
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -8,16 +8,17 @@ var expect = chai.expect;
 
 describe("loop.standaloneRoomViews", function() {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
   var FEEDBACK_STATES = loop.store.FEEDBACK_STATES;
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
   var sharedActions = loop.shared.actions;
+  var sharedUtils = loop.shared.utils;
 
   var sandbox, dispatcher, activeRoomStore, feedbackStore, dispatch;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     dispatcher = new loop.Dispatcher();
     dispatch = sandbox.stub(dispatcher, "dispatch");
     activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
@@ -91,16 +92,36 @@ describe("loop.standaloneRoomViews", fun
           location: "http://invalid.com",
           thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
         }]
       });
 
       expect(view.getDOMNode().querySelector(".standalone-context-url")).not.eql(null);
     });
 
+    it("should format the url for display", function() {
+      sandbox.stub(sharedUtils, "formatURL").returns({
+          location: "location",
+          hostname: "hostname"
+        });
+
+      var view = mountTestComponent({
+        roomName: "Mike's room",
+        roomContextUrls: [{
+          description: "Mark's super page",
+          location: "http://invalid.com",
+          thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+        }]
+      });
+
+      expect(view.getDOMNode()
+        .querySelector(".standalone-context-url-description-wrapper > a").textContent)
+        .eql("hostname");
+    });
+
     it("should not display context information if no urls are supplied", function() {
       var view = mountTestComponent({
         roomName: "Mike's room"
       });
 
       expect(view.getDOMNode().querySelector(".standalone-context-url")).eql(null);
     });
 
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/xpcshell/.eslintrc
@@ -0,0 +1,17 @@
+{
+  "ecmaFeatures": {
+    "arrowFunctions": true,
+    "blockBindings": true,
+    "destructuring": true,
+    "generators": true,
+    "restParams": true,
+    "spread": true,
+    "objectLiteralShorthandMethods": true,
+  },
+  "rules": {
+    "generator-star-spacing": [2, "after"],
+    // We should fix the errors and enable this (set to 2)
+    "no-var": 0,
+    "strict": [2, "global"]
+  }
+}
--- a/browser/components/loop/test/xpcshell/head.js
+++ b/browser/components/loop/test/xpcshell/head.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Http.jsm");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource:///modules/loop/MozLoopService.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
--- a/browser/components/loop/test/xpcshell/test_looppush_initialize.js
+++ b/browser/components/loop/test/xpcshell/test_looppush_initialize.js
@@ -1,222 +1,223 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
-{
-  let dummyCallback = () => {};
-  let mockWebSocket = new MockWebSocketChannel();
-  let pushServerRequestCount = 0;
+
+"use strict";
+
+let dummyCallback = () => {};
+let mockWebSocket = new MockWebSocketChannel();
+let pushServerRequestCount = 0;
 
-  add_test(function test_initalize_offline() {
-    Services.io.offline = true;
-    do_check_false(MozLoopPushHandler.initialize());
-    Services.io.offline = false;
-    run_next_test();
-  });
+add_test(function test_initalize_offline() {
+  Services.io.offline = true;
+  do_check_false(MozLoopPushHandler.initialize());
+  Services.io.offline = false;
+  run_next_test();
+});
 
-  add_test(function test_initalize_missing_chanid() {
-    Assert.throws(() => MozLoopPushHandler.register(null, dummyCallback, dummyCallback));
-    run_next_test();
-  });
+add_test(function test_initalize_missing_chanid() {
+  Assert.throws(() => MozLoopPushHandler.register(null, dummyCallback, dummyCallback));
+  run_next_test();
+});
 
-  add_test(function test_initalize_missing_regcallback() {
-    Assert.throws(() => MozLoopPushHandler.register("chan-1", null, dummyCallback));
-    run_next_test();
-  });
+add_test(function test_initalize_missing_regcallback() {
+  Assert.throws(() => MozLoopPushHandler.register("chan-1", null, dummyCallback));
+  run_next_test();
+});
 
-  add_test(function test_initalize_missing_notifycallback() {
-    Assert.throws(() => MozLoopPushHandler.register("chan-1", dummyCallback, null));
-    run_next_test();
-  });
+add_test(function test_initalize_missing_notifycallback() {
+  Assert.throws(() => MozLoopPushHandler.register("chan-1", dummyCallback, null));
+  run_next_test();
+});
 
-  add_test(function test_initalize_websocket() {
-    do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
-    MozLoopPushHandler.register(
-      "chan-1",
-      function(err, url, id) {
-        Assert.equal(err, null, "err should be null to indicate success");
-        Assert.equal(url, kEndPointUrl, "Should return push server application URL");
-        Assert.equal(id, "chan-1", "Should have channel id = chan-1");
-        Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
-                     "Should have the url from preferences");
-        Assert.equal(mockWebSocket.origin, kServerPushUrl,
-                     "Should have the origin url from preferences");
-        Assert.equal(mockWebSocket.protocol, "push-notification",
-                     "Should have the protocol set to push-notifications");
-        mockWebSocket.notify(15);
-      },
-      function(version, id) {
-        Assert.equal(version, 15, "Should have version number 15");
-        Assert.equal(id, "chan-1", "Should have channel id = chan-1");
-        run_next_test();
-      });
-  });
+add_test(function test_initalize_websocket() {
+  do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
+  MozLoopPushHandler.register(
+    "chan-1",
+    function(err, url, id) {
+      Assert.equal(err, null, "err should be null to indicate success");
+      Assert.equal(url, kEndPointUrl, "Should return push server application URL");
+      Assert.equal(id, "chan-1", "Should have channel id = chan-1");
+      Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
+                   "Should have the url from preferences");
+      Assert.equal(mockWebSocket.origin, kServerPushUrl,
+                   "Should have the origin url from preferences");
+      Assert.equal(mockWebSocket.protocol, "push-notification",
+                   "Should have the protocol set to push-notifications");
+      mockWebSocket.notify(15);
+    },
+    function(version, id) {
+      Assert.equal(version, 15, "Should have version number 15");
+      Assert.equal(id, "chan-1", "Should have channel id = chan-1");
+      run_next_test();
+    });
+});
 
-  add_test(function test_register_twice_same_channel() {
-    MozLoopPushHandler.register(
-      "chan-2",
-      function(err, url, id) {
-        Assert.equal(err, null, "Should return null for success");
-        Assert.equal(url, kEndPointUrl, "Should return push server application URL");
-        Assert.equal(id, "chan-2", "Should have channel id = chan-2");
-        Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
-                     "Should have the url from preferences");
-        Assert.equal(mockWebSocket.origin, kServerPushUrl,
-                     "Should have the origin url from preferences");
-        Assert.equal(mockWebSocket.protocol, "push-notification",
-                     "Should have the protocol set to push-notifications");
+add_test(function test_register_twice_same_channel() {
+  MozLoopPushHandler.register(
+    "chan-2",
+    function(err, url, id) {
+      Assert.equal(err, null, "Should return null for success");
+      Assert.equal(url, kEndPointUrl, "Should return push server application URL");
+      Assert.equal(id, "chan-2", "Should have channel id = chan-2");
+      Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
+                   "Should have the url from preferences");
+      Assert.equal(mockWebSocket.origin, kServerPushUrl,
+                   "Should have the origin url from preferences");
+      Assert.equal(mockWebSocket.protocol, "push-notification",
+                   "Should have the protocol set to push-notifications");
 
-        // Register again for the same channel
-        MozLoopPushHandler.register(
-          "chan-2",
-          function(err, url, id) {
-            Assert.equal(err, null, "Should return null for success");
-            Assert.equal(id, "chan-2", "Should have channel id = chan-2");
-            run_next_test();
-          },
-          dummyCallback
-        );
-      },
-      dummyCallback
-    );
-  });
+      // Register again for the same channel
+      MozLoopPushHandler.register(
+        "chan-2",
+        function(err, url, id) {
+          Assert.equal(err, null, "Should return null for success");
+          Assert.equal(id, "chan-2", "Should have channel id = chan-2");
+          run_next_test();
+        },
+        dummyCallback
+      );
+    },
+    dummyCallback
+  );
+});
 
-  // Test that the PushHander will re-connect after the near-end disconnect.
-  // The uaID is cleared to force re-registration of all notification channels.
-  add_test(function test_reconnect_websocket() {
-    MozLoopPushHandler.uaID = undefined;
-    mockWebSocket.stop();
-    // Previously registered onRegistration callbacks will fire and be checked (see above).
-  });
-
-  // Test that the PushHander will re-connect after the far-end disconnect.
-  // The uaID is cleared to force re-regsitration of all notification channels.
-  add_test(function test_reopen_websocket() {
-    MozLoopPushHandler.uaID = undefined;
-    MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
-    mockWebSocket.serverClose();
-    // Previously registered onRegistration callbacks will fire and be checked (see above).
-  });
+// Test that the PushHander will re-connect after the near-end disconnect.
+// The uaID is cleared to force re-registration of all notification channels.
+add_test(function test_reconnect_websocket() {
+  MozLoopPushHandler.uaID = undefined;
+  mockWebSocket.stop();
+  // Previously registered onRegistration callbacks will fire and be checked (see above).
+});
 
-  // Force a re-registration cycle and have the PushServer return a 500.
-  // A retry should occur and the registration then complete.
-  add_test(function test_retry_registration() {
-    MozLoopPushHandler.uaID = undefined;
-    mockWebSocket.initRegStatus = 500;
-    mockWebSocket.stop();
-  });
+// Test that the PushHander will re-connect after the far-end disconnect.
+// The uaID is cleared to force re-regsitration of all notification channels.
+add_test(function test_reopen_websocket() {
+  MozLoopPushHandler.uaID = undefined;
+  MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
+  mockWebSocket.serverClose();
+  // Previously registered onRegistration callbacks will fire and be checked (see above).
+});
+
+// Force a re-registration cycle and have the PushServer return a 500.
+// A retry should occur and the registration then complete.
+add_test(function test_retry_registration() {
+  MozLoopPushHandler.uaID = undefined;
+  mockWebSocket.initRegStatus = 500;
+  mockWebSocket.stop();
+});
 
-  add_test(function test_reconnect_no_registration() {
-    let regCnt = 0;
-    MozLoopPushHandler.shutdown();
-    MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
-    MozLoopPushHandler.register(
-      "test-chan",
-      function(err, url, id) {
-        Assert.equal(++regCnt, 1, "onRegistered should only be called once");
-        Assert.equal(err, null, "err should be null to indicate success");
-        Assert.equal(url, kEndPointUrl, "Should return push server application URL");
-        Assert.equal(id, "test-chan", "Should have channel id = test-chan");
-        mockWebSocket.stop();
-        setTimeout(run_next_test(), 0);
-      },
-      dummyCallback
-    );
-  });
+add_test(function test_reconnect_no_registration() {
+  let regCnt = 0;
+  MozLoopPushHandler.shutdown();
+  MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
+  MozLoopPushHandler.register(
+    "test-chan",
+    function(err, url, id) {
+      Assert.equal(++regCnt, 1, "onRegistered should only be called once");
+      Assert.equal(err, null, "err should be null to indicate success");
+      Assert.equal(url, kEndPointUrl, "Should return push server application URL");
+      Assert.equal(id, "test-chan", "Should have channel id = test-chan");
+      mockWebSocket.stop();
+      setTimeout(run_next_test(), 0);
+    },
+    dummyCallback
+  );
+});
 
-  add_test(function test_ping_websocket() {
-    let pingReceived = false,
-        socketClosed = false;
-    mockWebSocket.defaultMsgHandler = (msg) => {
-      pingReceived = true;
-      // Do not send a ping response.
-    }
-    mockWebSocket.close = () => {
-      socketClosed = true;
-    }
+add_test(function test_ping_websocket() {
+  let pingReceived = false,
+      socketClosed = false;
+  mockWebSocket.defaultMsgHandler = (msg) => {
+    pingReceived = true;
+    // Do not send a ping response.
+  }
+  mockWebSocket.close = () => {
+    socketClosed = true;
+  }
 
-    MozLoopPushHandler.shutdown();
-    MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
-    MozLoopPushHandler.register(
-      "test-chan",
-      function(err, url) {
-        Assert.equal(err, null, "err should be null to indicate success");
-        waitForCondition(() => pingReceived).then(() => {
-          waitForCondition(() => socketClosed).then(() => {
-            run_next_test();
-          }, () => {
-            do_throw("should have closed the websocket");
-          });
+  MozLoopPushHandler.shutdown();
+  MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
+  MozLoopPushHandler.register(
+    "test-chan",
+    function(err, url) {
+      Assert.equal(err, null, "err should be null to indicate success");
+      waitForCondition(() => pingReceived).then(() => {
+        waitForCondition(() => socketClosed).then(() => {
+          run_next_test();
         }, () => {
-          do_throw("should have sent ping");
+          do_throw("should have closed the websocket");
         });
-      },
-      dummyCallback
-    );
-  });
+      }, () => {
+        do_throw("should have sent ping");
+      });
+    },
+    dummyCallback
+  );
+});
 
-  add_test(function test_retry_pushurl() {
-    MozLoopPushHandler.shutdown();
-    loopServer.registerPathHandler("/push-server-config", (request, response) => {
-      // The PushHandler should retry the request for the push-server-config for
-      // each of these cases without throwing an error.
-      let n = 0;
-      switch (++pushServerRequestCount) {
-      case ++n:
-        // Non-200 response
-        response.setStatusLine(null, 500, "Retry");
-        response.processAsync();
-        response.finish();
-        break;
-      case ++n:
-        // missing parameter
-        response.setStatusLine(null, 200, "OK");
-        response.write(JSON.stringify({pushServerURI: null}));
-        response.processAsync();
-        response.finish();
-        break;
-      case ++n:
-        // json parse error
-        response.setStatusLine(null, 200, "OK");
-        response.processAsync();
-        response.finish();
-        break;
-      case ++n:
-        response.setStatusLine(null, 200, "OK");
-        response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
-        response.processAsync();
-        response.finish();
-
-        run_next_test();
-        break;
-      }
-    });
-
-    do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
-  });
-
-  function run_test() {
-    setupFakeLoopServer();
-
-    loopServer.registerPathHandler("/push-server-config", (request, response) => {
+add_test(function test_retry_pushurl() {
+  MozLoopPushHandler.shutdown();
+  loopServer.registerPathHandler("/push-server-config", (request, response) => {
+    // The PushHandler should retry the request for the push-server-config for
+    // each of these cases without throwing an error.
+    let n = 0;
+    switch (++pushServerRequestCount) {
+    case ++n:
+      // Non-200 response
+      response.setStatusLine(null, 500, "Retry");
+      response.processAsync();
+      response.finish();
+      break;
+    case ++n:
+      // missing parameter
+      response.setStatusLine(null, 200, "OK");
+      response.write(JSON.stringify({pushServerURI: null}));
+      response.processAsync();
+      response.finish();
+      break;
+    case ++n:
+      // json parse error
+      response.setStatusLine(null, 200, "OK");
+      response.processAsync();
+      response.finish();
+      break;
+    case ++n:
       response.setStatusLine(null, 200, "OK");
       response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
       response.processAsync();
       response.finish();
-    });
+
+      run_next_test();
+      break;
+    }
+  });
 
-    Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
-    Services.prefs.setIntPref("loop.retry_delay.start", 10); // 10 ms
-    Services.prefs.setIntPref("loop.retry_delay.limit", 20); // 20 ms
-    Services.prefs.setIntPref("loop.ping.interval", 50); // 50 ms
-    Services.prefs.setIntPref("loop.ping.timeout", 20); // 20 ms
+  do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
+});
+
+function run_test() {
+  setupFakeLoopServer();
 
-    do_register_cleanup(function() {
-      Services.prefs.clearUserPref("services.push.serverULR");
-      Services.prefs.clearUserPref("loop.retry_delay.start");
-      Services.prefs.clearUserPref("loop.retry_delay.limit");
-      Services.prefs.clearUserPref("loop.ping.interval");
-      Services.prefs.clearUserPref("loop.ping.timeout");
-    });
+  loopServer.registerPathHandler("/push-server-config", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+    response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
+    response.processAsync();
+    response.finish();
+  });
 
-    run_next_test();
-  };
-}
+  Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
+  Services.prefs.setIntPref("loop.retry_delay.start", 10); // 10 ms
+  Services.prefs.setIntPref("loop.retry_delay.limit", 20); // 20 ms
+  Services.prefs.setIntPref("loop.ping.interval", 50); // 50 ms
+  Services.prefs.setIntPref("loop.ping.timeout", 20); // 20 ms
+
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref("services.push.serverULR");
+    Services.prefs.clearUserPref("loop.retry_delay.start");
+    Services.prefs.clearUserPref("loop.retry_delay.limit");
+    Services.prefs.clearUserPref("loop.ping.interval");
+    Services.prefs.clearUserPref("loop.ping.timeout");
+  });
+
+  run_next_test();
+};
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -1,12 +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/. */
 
+"use strict";
+
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource:///modules/loop/LoopRooms.jsm");
 Cu.import("resource:///modules/Chat.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 let openChatOrig = Chat.open;
 
 const kContextEnabledPref = "loop.contextInConverations.enabled";
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -1,12 +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/. */
 
+"use strict";
+
 const { LoopCallsInternal } = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 
 let actionReceived = false;
 let openChatOrig = Chat.open;
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_directcall.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_directcall.js
@@ -1,12 +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/. */
 
+"use strict";
+
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 let openChatOrig = Chat.open;
 
 const contact = {
   name: [ "Mr Smith" ],
   email: [{
     type: "home",
--- a/browser/components/loop/test/xpcshell/test_loopservice_dnd.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_dnd.js
@@ -1,12 +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/. */
 
+"use strict";
+
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 let openChatOrig = Chat.open;
 
 add_test(function test_get_do_not_disturb() {
   Services.prefs.setBoolPref("loop.do_not_disturb", false);
 
   do_check_false(MozLoopService.doNotDisturb);
@@ -36,17 +38,17 @@ add_test(function test_do_not_disturb_di
   MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
     let opened = false;
     Chat.open = function() {
       opened = true;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
 
-    waitForCondition(function() opened).then(() => {
+    waitForCondition(() => opened).then(() => {
       run_next_test();
     }, () => {
       do_throw("should have opened a chat window");
     });
   });
 });
 
 add_test(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
--- a/browser/components/loop/test/xpcshell/test_loopservice_encryptionkey.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_encryptionkey.js
@@ -1,12 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* global Services, Assert */
 
+"use strict";
+
 const kGuestKeyPref = "loop.key";
 const kFxAKeyPref = "loop.key.fxa";
 
 do_register_cleanup(function() {
   Services.prefs.clearUserPref(kGuestKeyPref);
   MozLoopServiceInternal.fxAOAuthTokenData = null;
   MozLoopServiceInternal.fxAOAuthProfile = null;
 });
--- a/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
@@ -1,18 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 let startTimerCalled = false;
 
 /**
  * Tests that registration doesn't happen when the expiry time is
  * not set.
  */
-add_task(function test_initialize_no_expiry() {
+add_task(function* test_initialize_no_expiry() {
   startTimerCalled = false;
 
   let initializedPromise = yield MozLoopService.initialize();
   Assert.equal(initializedPromise, "registration not needed",
                "Promise should be fulfilled");
   Assert.equal(startTimerCalled, false,
     "should not register when no expiry time is set");
 });
--- a/browser/components/loop/test/xpcshell/test_loopservice_locales.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_locales.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 function test_locale() {
   // Set the pref to something controlled.
   Services.prefs.setCharPref("general.useragent.locale", "ab-CD");
 
   Assert.equal(MozLoopService.locale, "ab-CD");
 
   Services.prefs.clearUserPref("general.useragent.locale");
 }
--- a/browser/components/loop/test/xpcshell/test_loopservice_loop_prefs.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_loop_prefs.js
@@ -1,12 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /*global XPCOMUtils, Services, Assert */
 
+"use strict";
+
 var fakeCharPrefName = "color";
 var fakeBoolPrefName = "boolean";
 var fakePrefValue = "green";
 
 function test_getLoopPref()
 {
   Services.prefs.setCharPref("loop." + fakeCharPrefName, fakePrefValue);
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_notification.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_notification.js
@@ -1,12 +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/. */
 
+"use strict";
+
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 
 let openChatOrig = Chat.open;
 
 add_test(function test_openChatWindow_on_notification() {
   Services.prefs.setCharPref("loop.seenToS", "unseen");
 
@@ -15,17 +17,17 @@ add_test(function test_openChatWindow_on
   MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
     let opened = false;
     Chat.open = function() {
       opened = true;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
 
-    waitForCondition(function() opened).then(() => {
+    waitForCondition(() => opened).then(() => {
       do_check_true(opened, "should open a chat window");
 
       do_check_eq(Services.prefs.getCharPref("loop.seenToS"), "seen",
                   "should set the pref to 'seen'");
 
       run_next_test();
     }, () => {
       do_throw("should have opened a chat window");
--- a/browser/components/loop/test/xpcshell/test_loopservice_registration.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_registration.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 Cu.import("resource://services-common/utils.js");
 
 /**
  * This file is to test general registration. Note that once successful
  * registration has taken place, we can no longer test the server side
  * parts as the service protects against this, hence, they need testing in
  * other test files.
  */
--- a/browser/components/loop/test/xpcshell/test_loopservice_registration_retry.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_registration_retry.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://services-common/utils.js");
 
 /**
  * Tests that it's possible to retry registration after an error.
  */
 
 add_test(function test_retry_after_failed_push_reg() {
--- a/browser/components/loop/test/xpcshell/test_loopservice_restart.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const FAKE_FXA_TOKEN_DATA = JSON.stringify({
   "token_type": "bearer",
   "access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
   "scope": "profile"
 });
 const FAKE_FXA_PROFILE = JSON.stringify({
   "email": "test@example.com",
   "uid": "999999994d9f4b08a2cbfc0999999999",
@@ -15,47 +17,47 @@ const LOOP_FXA_TOKEN_PREF = "loop.fxa_oa
 const LOOP_FXA_PROFILE_PREF = "loop.fxa_oauth.profile";
 const LOOP_CREATED_ROOM_PREF = "loop.createdRoom";
 const LOOP_INITIAL_DELAY_PREF = "loop.initialDelay";
 
 /**
  * This file is to test restart+reauth.
  */
 
-add_task(function test_initialize_with_no_guest_rooms_and_no_auth_token() {
+add_task(function* test_initialize_with_no_guest_rooms_and_no_auth_token() {
   // Set time to be 2 seconds in the past.
   var nowSeconds = Date.now() / 1000;
   Services.prefs.setBoolPref(LOOP_CREATED_ROOM_PREF, false);
   Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
 
   yield MozLoopService.initialize().then((msg) => {
     Assert.equal(msg, "registration not needed", "Initialize should not register when the " +
                                                  "URLs are expired and there are no auth tokens");
   }, (error) => {
     Assert.ok(false, error, "should have resolved the promise that initialize returned");
   });
 });
 
-add_task(function test_initialize_with_created_room_and_no_auth_token() {
+add_task(function* test_initialize_with_created_room_and_no_auth_token() {
   Services.prefs.setBoolPref(LOOP_CREATED_ROOM_PREF, true);
   Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
 
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 200, "OK");
   });
 
   yield MozLoopService.initialize().then((msg) => {
     Assert.equal(msg, "initialized without FxA status", "Initialize should register as a " +
                                                      "guest when no auth tokens but expired URLs");
   }, (error) => {
     Assert.ok(false, error, "should have resolved the promise that initialize returned");
   });
 });
 
-add_task(function test_initialize_with_invalid_fxa_token() {
+add_task(function* test_initialize_with_invalid_fxa_token() {
   Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
   Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
 
   // Only need to implement the FxA registration because the previous
   // test registered as a guest.
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 401, "Unauthorized");
     response.write(JSON.stringify({
@@ -78,17 +80,17 @@ add_task(function test_initialize_with_i
     Assert.ok(MozLoopServiceInternal.errors.has("login"),
               "Initialization error should have been reported to UI");
     Assert.ok(MozLoopServiceInternal.errors.has("login"));
     Assert.ok(MozLoopServiceInternal.errors.get("login").friendlyDetailsButtonCallback,
               "Check that there is a retry callback");
   });
 });
 
-add_task(function test_initialize_with_fxa_token() {
+add_task(function* test_initialize_with_fxa_token() {
   Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
   Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
 
   MozLoopService.errors.clear();
 
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 200, "OK");
   });
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 const LOOP_HAWK_PREF = "loop.hawk-session-token";
 const fakeSessionToken1 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1751";
 const fakeSessionToken2 = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750";
 
 add_test(function test_registration_invalid_token() {
   Services.prefs.setCharPref(LOOP_HAWK_PREF, fakeSessionToken1);
   var authorizationAttempts = 0;
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_save.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_save.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 /**
  * Test that things behave reasonably when a reasonable Hawk-Session-Token
  * header is returned with the registration response.
  */
 add_test(function test_registration_returns_hawk_session_token() {
   var fakeSessionToken = "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750";
   Services.prefs.clearUserPref("loop.hawk-session-token");
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_send.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_send.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 add_test(function test_registration_uses_hawk_session_token() {
   Services.prefs.setCharPref("loop.hawk-session-token",
     "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1750");
 
   loopServer.registerPathHandler("/registration", (request, response) => {
     // Check that we have an Authorization header with the correct bits. The
     // translation of values are tested in different modules, for the components
     // that we use.
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 // XXX should report error if Hawk-Session-Token is lexically invalid
 // (not a string of 64 hex digits) to help resist other possible injection
 // attacks.  For now, however, we're just checking if it's the right length.
 add_test(function test_registration_handles_bogus_hawk_token() {
 
   var wrongSizeToken = "jdkasjkasjdlaksj";
   Services.prefs.clearUserPref("loop.hawk-session-token");
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2806,18 +2806,17 @@ let E10SUINotification = {
 
       if (!activationNoticeShown) {
         this._showE10sActivatedNotice();
       }
 
       // e10s doesn't work with accessibility, so we prompt to disable
       // e10s if a11y is enabled, now or in the future.
       Services.obs.addObserver(this, "a11y-init-or-shutdown", true);
-      if (Services.appinfo.accessibilityEnabled &&
-          !Services.appinfo.accessibilityIsUIA) {
+      if (Services.appinfo.accessibilityIsBlacklistedForE10S) {
         this._showE10sAccessibilityWarning();
       }
     } else {
       let displayFeedbackRequest = false;
       try {
         displayFeedbackRequest = Services.prefs.getBoolPref("browser.requestE10sFeedback");
       } catch (e) {}
 
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -999,22 +999,24 @@ let gEditItemOverlay = {
     }
   },
 
   // nsINavBookmarkObserver
   onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
                 aLastModified, aItemType) {
     if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow"))
       this._onTagsChange(aItemId);
-    else if (this._paneInfo.isItem && aProperty == "title")
-      this._onItemTitleChange(aItemId, aValue);
-    else (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId)
+    else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId)
       return;
 
     switch (aProperty) {
+    case "title":
+      if (this._paneInfo.isItem)
+        this._onItemTitleChange(aItemId, aValue);
+      break;
     case "uri":
       let newURI = NetUtil.newURI(aValue);
       if (!newURI.equals(this._paneInfo.uri)) {
         this._paneInfo.uri = newURI;
         if (this._paneInfo.visibleRows.has("locationRow"))
           this._initLocationField();
 
         if (this._paneInfo.visibleRows.has("tagsRow")) {
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -1463,16 +1463,22 @@ this.UITour = {
     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");
+    return tooltip.getAttribute("targetName") == aTargetName && tooltip.state != "closed";
+  },
+
   hideInfo: function(aWindow) {
     let document = aWindow.document;
 
     let tooltip = document.getElementById("UITourTooltip");
     this._removeAnnotationPanelMutationObserver(tooltip);
     tooltip.hidePopup();
     this._setAppMenuStateForAnnotation(aWindow, "info", false);
 
--- a/browser/devtools/performance/modules/graphs.js
+++ b/browser/devtools/performance/modules/graphs.js
@@ -21,16 +21,19 @@ loader.lazyRequireGetter(this, "Profiler
   "devtools/shared/profiler/global");
 loader.lazyRequireGetter(this, "TimelineGlobal",
   "devtools/shared/timeline/global");
 loader.lazyRequireGetter(this, "MarkersOverview",
   "devtools/shared/timeline/markers-overview", true);
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
+// TODO get rid of retro mode in bug 1160313
+loader.lazyRequireGetter(this, "Services");
+
 /**
  * For line graphs
  */
 const HEIGHT = 35; // px
 const STROKE_WIDTH = 1; // px
 const DAMPEN_VALUES = 0.95;
 const CLIPHEAD_LINE_COLOR = "#666";
 const SELECTION_LINE_COLOR = "#555";
@@ -160,29 +163,49 @@ const GRAPH_DEFINITIONS = {
   timeline: {
     constructor: TimelineGraph,
     selector: "#markers-overview",
     needsBlueprints: true,
     primaryLink: true
   }
 };
 
+// TODO get rid of retro mode in bug 1160313
+const GRAPH_DEFINITIONS_RETRO = {
+  memory: {
+    constructor: MemoryGraph,
+    selector: "#memory-overview",
+  },
+  framerate: {
+    constructor: FramerateGraph,
+    selector: "#time-framerate",
+    needsBlueprints: true,
+    primaryLink: true
+  },
+  timeline: {
+    constructor: TimelineGraph,
+    selector: "#markers-overview",
+  }
+};
+
 /**
  * A controller for orchestrating the performance's tool overview graphs. Constructs,
  * syncs, toggles displays and defines the memory, framerate and timeline view.
  *
  * @param {object} definition
  * @param {DOMElement} root
  * @param {function} getBlueprint
  * @param {function} getTheme
  */
 function GraphsController ({ definition, root, getBlueprint, getTheme }) {
   this._graphs = {};
   this._enabled = new Set();
-  this._definition = definition || GRAPH_DEFINITIONS;
+  // TODO get rid of retro mode in bug 1160313
+  let RETRO_MODE = Services.prefs.getBoolPref("devtools.performance.ui.retro-mode");
+  this._definition = definition || (RETRO_MODE ? GRAPH_DEFINITIONS_RETRO : GRAPH_DEFINITIONS);
   this._root = root;
   this._getBlueprint = getBlueprint;
   this._getTheme = getTheme;
   this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0];
   this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
 
   EventEmitter.decorate(this);
   this._onSelecting = this._onSelecting.bind(this);
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -279,21 +279,26 @@ let PerformanceController = {
     this._nonBooleanPrefs[prefName] = prefValue;
   },
 
   /**
    * Starts recording with the PerformanceFront. Emits `EVENTS.RECORDING_STARTED`
    * when the front has started to record.
    */
   startRecording: Task.async(function *() {
+    // Store retro-mode here so we can easily list true/false
+    // values for reverting.
+    // TODO bug 1160313
+    let superMode = !this.getOption("retro-mode");
+
     let options = {
-      withMarkers: true,
-      withMemory: this.getOption("enable-memory"),
+      withMarkers: superMode ? true : false,
+      withMemory: superMode ? this.getOption("enable-memory") : false,
       withTicks: this.getOption("enable-framerate"),
-      withAllocations: this.getOption("enable-memory"),
+      withAllocations: superMode ? this.getOption("enable-memory") : false,
       allocationsSampleProbability: this.getPref("memory-sample-probability"),
       allocationsMaxLogLength: this.getPref("memory-max-log-length"),
       bufferSize: this.getPref("profiler-buffer-size"),
       sampleFrequency: this.getPref("profiler-sample-frequency")
     };
 
     this.emit(EVENTS.RECORDING_WILL_START);
 
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -117,10 +117,15 @@ support-files =
 [browser_profiler_tree-view-01.js]
 [browser_profiler_tree-view-02.js]
 [browser_profiler_tree-view-03.js]
 [browser_profiler_tree-view-04.js]
 [browser_profiler_tree-view-05.js]
 [browser_profiler_tree-view-06.js]
 [browser_profiler_tree-view-07.js]
 [browser_profiler_tree-view-08.js]
-[browser_timeline_blueprint.js]
-[browser_timeline_filters.js]
+[browser_timeline-blueprint.js]
+[browser_timeline-filters.js]
+[browser_timeline-waterfall-background.js]
+[browser_timeline-waterfall-generic.js]
+[browser_timeline-waterfall-sidebar.js]
+# remove in bug 1160313
+[browser_retro-test.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_retro-test.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that only js-calltree view is on, default, and many things are hidden
+ * when in retro mode.
+ */
+const HIDDEN_OPTIONS = ["option-enable-memory", "option-invert-flame-graph", "option-show-jit-optimizations", "option-flatten-tree-recursion"];
+
+Services.prefs.setBoolPref("devtools.performance.ui.retro-mode", true);
+
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, DetailsView, PerformanceController, $, $$, JsCallTreeView } = panel.panelWin;
+
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
+  let model = PerformanceController.getCurrentRecording();
+
+  ok(model.getMemory().length === 0, "model did not record memory.");
+  ok(model.getTicks().length !== 0, "model did get ticks.");
+  ok(model.getAllocations().sites.length === 0, "model did get allocation data.");
+  ok(model.getAllocations().timestamps.length === 0, "model did get allocation data.");
+  ok(model.getAllocations().frames.length === 0, "model did get allocation data.");
+  ok(model.getAllocations().counts.length === 0, "model did get allocation data.");
+
+  ok(DetailsView.isViewSelected(JsCallTreeView),
+    "The jscalltree view is selected by default");
+
+  for (let option of $$("#performance-options-menupopup > menuitem")) {
+    if (HIDDEN_OPTIONS.indexOf(option.id) !== -1) {
+      ok(option.hidden === true, `${option.id} should be hidden.`);
+    } else {
+      ok(option.hidden === false, `${option.id} should be visible.`);
+    }
+  }
+
+  for (let viewbutton of $$("#performance-toolbar-controls-detail-views > toolbarbutton")) {
+    ok (viewbutton.hidden === true, `${viewbutton.id} should be hidden.`);
+  }
+
+  ok($("#markers-overview").hidden, "markers overview should be hidden.");
+  ok($("#memory-overview").hidden, "memory overview should be hidden.");
+  ok(!$("#time-framerate").hidden, "framerate should be shown.");
+
+  yield teardown(panel);
+  finish();
+}
rename from browser/devtools/performance/test/browser_timeline_blueprint.js
rename to browser/devtools/performance/test/browser_timeline-blueprint.js
rename from browser/devtools/performance/test/browser_timeline_filters.js
rename to browser/devtools/performance/test/browser_timeline-filters.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_timeline-waterfall-background.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the waterfall background is a 1px high canvas stretching across
+ * the container bounds.
+ */
+
+function spawnTest () {
+  let { target, panel } = yield initPerformance(SIMPLE_URL);
+  let { $, EVENTS, PerformanceController, OverviewView, DetailsView, WaterfallView } = panel.panelWin;
+
+  yield startRecording(panel);
+  ok(true, "Recording has started.");
+
+  let updated = 0;
+  OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
+
+  ok((yield waitUntil(() => updated > 0)),
+    "The overview graphs were updated a bunch of times.");
+  ok((yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length > 0)),
+    "There are some markers available.");
+
+  let rendered = Promise.all([
+    DetailsView.selectView("waterfall"),
+    once(WaterfallView, EVENTS.WATERFALL_RENDERED)
+  ]);
+  yield stopRecording(panel);
+  ok(true, "Recording has ended.");
+  yield rendered;
+
+  // Test the waterfall background.
+
+  let parentWidth = $("#waterfall-view").getBoundingClientRect().width;
+  let sidebarWidth = $(".waterfall-sidebar").getBoundingClientRect().width;
+  let detailsWidth = $("#waterfall-details").getBoundingClientRect().width;
+  let waterfallWidth = WaterfallView.waterfall._waterfallWidth;
+  is(waterfallWidth, parentWidth - sidebarWidth - detailsWidth,
+    "The waterfall width is correct.")
+
+  ok(WaterfallView.waterfall._canvas,
+    "A canvas should be created after the recording ended.");
+  ok(WaterfallView.waterfall._ctx,
+    "A 2d context should be created after the recording ended.");
+
+  is(WaterfallView.waterfall._canvas.width, waterfallWidth,
+    "The canvas width is correct.");
+  is(WaterfallView.waterfall._canvas.height, 1,
+    "The canvas height is correct.");
+
+  yield teardown(panel);
+  finish();
+}
rename from browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
rename to browser/devtools/performance/test/browser_timeline-waterfall-generic.js
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
+++ b/browser/devtools/performance/test/browser_timeline-waterfall-generic.js
@@ -1,31 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the waterfall is properly built after finishing a recording.
  */
 
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, $$, EVENTS, TimelineController } = panel.panelWin;
+function spawnTest () {
+  let { target, panel } = yield initPerformance(SIMPLE_URL);
+  let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
 
-  yield TimelineController.toggleRecording();
+  yield startRecording(panel);
   ok(true, "Recording has started.");
 
   let updated = 0;
-  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
+  OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
 
   ok((yield waitUntil(() => updated > 0)),
     "The overview graphs were updated a bunch of times.");
-  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
+  ok((yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length > 0)),
     "There are some markers available.");
 
-  yield TimelineController.toggleRecording();
+  yield stopRecording(panel);
   ok(true, "Recording has ended.");
 
   // Test the header container.
 
   ok($(".waterfall-header-container"),
     "A header container should have been created.");
 
   // Test the header sidebar (left).
@@ -57,9 +57,11 @@ add_task(function*() {
     "Some marker name labels should have been created inside the sidebar.");
 
   // Test the markers waterfall (right).
 
   ok($$(".waterfall-marker-item").length,
     "Some marker waterfall nodes should have been created.");
   ok($$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar").length,
     "Some marker color bars should have been created inside the waterfall.");
-});
+  yield teardown(panel);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_timeline-waterfall-sidebar.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the sidebar is properly updated when a marker is selected.
+ */
+
+function spawnTest () {
+  let { target, panel } = yield initPerformance(SIMPLE_URL);
+  let { $, $$, EVENTS, PerformanceController, OverviewView } = panel.panelWin;
+  let { L10N, TIMELINE_BLUEPRINT } = devtools.require("devtools/shared/timeline/global");
+
+  yield startRecording(panel);
+  ok(true, "Recording has started.");
+
+  yield waitUntil(() => {
+    // Wait until we get 3 different markers.
+    let markers = PerformanceController.getCurrentRecording().getMarkers();
+    return markers.some(m => m.name == "Styles") &&
+           markers.some(m => m.name == "Reflow") &&
+           markers.some(m => m.name == "Paint");
+  });
+
+  yield stopRecording(panel);
+  ok(true, "Recording has ended.");
+
+  // Select everything
+  OverviewView.graphs.get("timeline").setSelection({ start: 0, end: OverviewView.graphs.get("timeline").width })
+
+  let bars = $$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar");
+  let markers = PerformanceController.getCurrentRecording().getMarkers();
+
+  ok(bars.length > 2, "got at least 3 markers");
+
+  let sidebar = $("#waterfall-details");
+  for (let i = 0; i < bars.length; i++) {
+    let bar = bars[i];
+    bar.click();
+    let m = markers[i];
+
+    let name = TIMELINE_BLUEPRINT[m.name].label;
+
+    is($("#waterfall-details .marker-details-type").getAttribute("value"), name,
+      "sidebar title matches markers name");
+
+    let printedStartTime = $(".marker-details-start .marker-details-labelvalue").getAttribute("value");
+    let printedEndTime = $(".marker-details-end .marker-details-labelvalue").getAttribute("value");
+    let printedDuration= $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
+
+    let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
+
+    // Values are rounded. We don't use a strict equality.
+    is(toMs(m.start), printedStartTime, "sidebar start time is valid");
+    is(toMs(m.end), printedEndTime, "sidebar end time is valid");
+    is(toMs(m.end - m.start), printedDuration, "sidebar duration is valid");
+  }
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -51,27 +51,32 @@ let DEFAULT_PREFS = [
   "devtools.performance.ui.show-idle-blocks",
   "devtools.performance.ui.enable-memory",
   "devtools.performance.ui.enable-framerate",
   "devtools.performance.ui.show-jit-optimizations",
   "devtools.performance.memory.sample-probability",
   "devtools.performance.memory.max-log-length",
   "devtools.performance.profiler.buffer-size",
   "devtools.performance.profiler.sample-frequency-khz",
+  "devtools.performance.ui.retro-mode",
 ].reduce((prefs, pref) => {
   prefs[pref] = Preferences.get(pref);
   return prefs;
 }, {});
 
 // Enable the new performance panel for all tests.
 Services.prefs.setBoolPref("devtools.performance.enabled", true);
 // Enable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
+// Disable retro mode.
+// TODO bug 1160313
+Services.prefs.setBoolPref("devtools.performance.ui.retro-mode", false);
+
 /**
  * Call manually in tests that use frame script utils after initializing
  * the tool. Must be called after initializing so we can detect
  * whether or not `content` is a CPOW or not. Call after init but before navigating
  * to different pages.
  */
 function loadFrameScripts () {
   mm = gBrowser.selectedBrowser.messageManager;
--- a/browser/devtools/performance/views/details-abstract-subview.js
+++ b/browser/devtools/performance/views/details-abstract-subview.js
@@ -67,16 +67,22 @@ let DetailsSubview = {
 
   /**
    * An array of preferences under `devtools.performance.` that the view should
    * observe and callback `this._onObservedPrefChange` upon change.
    */
   observedPrefs: [],
 
   /**
+   * Flag specifying if this view should update while the overview selection
+   * area is actively being dragged by the mouse.
+   */
+  shouldUpdateWhileMouseIsActive: false,
+
+  /**
    * Called when recording stops or is selected.
    */
   _onRecordingStoppedOrSelected: function(_, recording) {
     if (!recording || recording.isRecording()) {
       return;
     }
     if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
       this.render();
@@ -85,17 +91,24 @@ let DetailsSubview = {
     }
   },
 
   /**
    * Fired when a range is selected or cleared in the OverviewView.
    */
   _onOverviewRangeChange: function (_, interval) {
     if (DetailsView.isViewSelected(this)) {
-      let debounced = () => this.render(interval);
+      let debounced = () => {
+        if (!this.shouldUpdateWhileMouseIsActive && OverviewView.isMouseActive) {
+          // Don't render yet, while the selection is still being dragged.
+          setNamedTimeout("range-change-debounce", this.rangeChangeDebounceTime, debounced);
+        } else {
+          this.render(interval);
+        }
+      };
       setNamedTimeout("range-change-debounce", this.rangeChangeDebounceTime, debounced);
     } else {
       this.shouldUpdateWhenShown = true;
     }
   },
 
   /**
    * Fired when a view is selected in the DetailsView.
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -8,17 +8,17 @@
  */
 let JsCallTreeView = Heritage.extend(DetailsSubview, {
 
   rerenderPrefs: [
     "invert-call-tree",
     "show-platform-data"
   ],
 
-  rangeChangeDebounceTime: 50, // ms
+  rangeChangeDebounceTime: 75, // ms
 
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
     DetailsSubview.initialize.call(this);
 
     this._onPrefChanged = this._onPrefChanged.bind(this);
--- a/browser/devtools/performance/views/details-js-flamegraph.js
+++ b/browser/devtools/performance/views/details-js-flamegraph.js
@@ -4,16 +4,18 @@
 "use strict";
 
 /**
  * FlameGraph view containing a pyramid-like visualization of a profile,
  * controlled by DetailsView.
  */
 let JsFlameGraphView = Heritage.extend(DetailsSubview, {
 
+  shouldUpdateWhileMouseIsActive: true,
+
   rerenderPrefs: [
     "invert-flame-graph",
     "flatten-tree-recursion",
     "show-platform-data",
     "show-idle-blocks"
   ],
 
   /**
--- a/browser/devtools/performance/views/details-memory-flamegraph.js
+++ b/browser/devtools/performance/views/details-memory-flamegraph.js
@@ -4,16 +4,18 @@
 "use strict";
 
 /**
  * FlameGraph view containing a pyramid-like visualization of memory allocation
  * sites, controlled by DetailsView.
  */
 let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
 
+  shouldUpdateWhileMouseIsActive: true,
+
   rerenderPrefs: [
     "invert-flame-graph",
     "flatten-tree-recursion",
     "show-idle-blocks"
   ],
 
   /**
    * Sets up the view with event binding.
--- a/browser/devtools/performance/views/details-waterfall.js
+++ b/browser/devtools/performance/views/details-waterfall.js
@@ -11,17 +11,17 @@ let WaterfallView = Heritage.extend(Deta
   observedPrefs: [
     "hidden-markers"
   ],
 
   rerenderPrefs: [
     "hidden-markers"
   ],
 
-  rangeChangeDebounceTime: 10, // ms
+  rangeChangeDebounceTime: 75, // ms
 
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
     DetailsSubview.initialize.call(this);
 
     this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
@@ -69,16 +69,21 @@ let WaterfallView = Heritage.extend(Deta
   },
 
   /**
    * Called when a marker is selected in the waterfall view,
    * updating the markers detail view.
    */
   _onMarkerSelected: function (event, marker) {
     let recording = PerformanceController.getCurrentRecording();
+    // Race condition in tests due to lazy rendering of markers in the
+    // waterfall? intermittent bug 1157523
+    if (!recording) {
+      return;
+    }
     let frames = recording.getFrames();
 
     if (event === "selected") {
       this.details.render({ toolbox: gToolbox, marker, frames });
     }
     if (event === "unselected") {
       this.details.empty();
     }
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -89,19 +89,22 @@ let DetailsView = {
    * if currently selected. Called when a preference changes in `devtools.performance.ui.`.
    */
   setAvailableViews: Task.async(function* () {
     let recording = PerformanceController.getCurrentRecording();
     let isRecording = recording && recording.isRecording();
     let invalidCurrentView = false;
 
     for (let [name, { view }] of Iterator(this.components)) {
-      let isSupported = this._isViewSupported(name, false);
+      // TODO bug 1160313 get rid of retro mode checks.
+      let isRetro = PerformanceController.getOption("retro-mode");
+      let isSupported = isRetro ? name === "js-calltree" : this._isViewSupported(name, false);
 
-      $(`toolbarbutton[data-view=${name}]`).hidden = !isSupported;
+      // TODO bug 1160313 hide all view buttons, but let js-calltree still be "supported"
+      $(`toolbarbutton[data-view=${name}]`).hidden = isRetro ? true : !isSupported;
 
       // If the view is currently selected and not supported, go back to the
       // default view.
       if (!isSupported && this.isViewSelected(view)) {
         invalidCurrentView = true;
       }
     }
 
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -88,16 +88,26 @@ let OverviewView = {
     PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.off(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
     this.graphs.off("selecting", this._onGraphSelecting);
     this.graphs.off("rendered", this._onGraphRendered);
     yield this.graphs.destroy();
   }),
 
   /**
+   * Returns true if any of the overview graphs have mouse dragging active,
+   * false otherwise.
+   */
+  get isMouseActive() {
+    return (this.markersOverview && this.markersOverview.isMouseActive) ||
+           (this.memoryOverview && this.memoryOverview.isMouseActive) ||
+           (this.framerateGraph && this.framerateGraph.isMouseActive);
+  },
+
+  /**
    * Disabled in the event we're using a Timeline mock, so we'll have no
    * timeline, ticks or memory data to show, so just block rendering and hide
    * the panel.
    */
   disable: function () {
     this._disabled = true;
     this.graphs.disableAll();
   },
--- a/browser/devtools/performance/views/toolbar.js
+++ b/browser/devtools/performance/views/toolbar.js
@@ -16,16 +16,29 @@ let ToolbarView = {
     this._onHiddenMarkersChanged = this._onHiddenMarkersChanged.bind(this);
     this._onPrefChanged = this._onPrefChanged.bind(this);
 
     this.optionsView = new OptionsView({
       branchName: BRANCH_NAME,
       menupopup: $("#performance-options-menupopup")
     });
 
+    // TODO bug 1160313 get rid of retro mode checks
+    // hide option buttons here, and any other buttons in the toolbar
+    // (details.js takes care of view buttons)
+    if (PerformanceController.getOption("retro-mode")) {
+      let RETRO_ELEMENTS = [
+        "#option-flatten-tree-recursion", "#option-enable-memory", "#option-invert-flame-graph",
+        "#option-show-jit-optimizations", "#filter-button"
+      ];
+      for (let selector of RETRO_ELEMENTS) {
+        $(selector).hidden = true;
+      }
+    }
+
     yield this.optionsView.initialize();
     this.optionsView.on("pref-changed", this._onPrefChanged);
 
     this._buildMarkersFilterPopup();
     this._updateHiddenMarkersPopup();
     $("#performance-filter-menupopup").addEventListener("popupshowing", this._onFilterPopupShowing);
     $("#performance-filter-menupopup").addEventListener("popuphiding",  this._onFilterPopupHiding);
   }),
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -216,16 +216,24 @@ AbstractCanvasGraph.prototype = {
   get width() {
     return this._width;
   },
   get height() {
     return this._height;
   },
 
   /**
+   * Return true if the mouse is actively messing with the selection, false
+   * otherwise.
+   */
+  get isMouseActive() {
+    return this._isMouseActive;
+  },
+
+  /**
    * Returns a promise resolved once this graph is ready to receive data.
    */
   ready: function() {
     return this._ready.promise;
   },
 
   /**
    * Destroys this graph.
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser.ini
+++ /dev/null
@@ -1,19 +0,0 @@
-[DEFAULT]
-tags = devtools
-subsuite = devtools
-support-files =
-  doc_simple-test.html
-  head.js
-
-[browser_timeline_aaa_run_first_leaktest.js]
-[browser_timeline_overview-initial-selection-01.js]
-[browser_timeline_overview-initial-selection-02.js]
-[browser_timeline_overview-update.js]
-[browser_timeline_overview-theme.js]
-[browser_timeline_panels.js]
-[browser_timeline_recording-without-memory.js]
-[browser_timeline_recording.js]
-[browser_timeline_waterfall-background.js]
-[browser_timeline_waterfall-generic.js]
-[browser_timeline_waterfall-styles.js]
-[browser_timeline_waterfall-sidebar.js]
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_aaa_run_first_leaktest.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the timeline leaks on initialization and sudden destruction.
- * You can also use this initialization format as a template for other tests.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-
-  ok(target, "Should have a target available.");
-  ok(panel, "Should have a panel available.");
-
-  ok(panel.panelWin.gToolbox, "Should have a toolbox reference on the panel window.");
-  ok(panel.panelWin.gTarget, "Should have a target reference on the panel window.");
-  ok(panel.panelWin.gFront, "Should have a front reference on the panel window.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-01.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the overview has an initial selection when recording has finished
- * and there is data available.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
-  let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
-
-  $("#memory-checkbox").checked = true;
-  yield TimelineController.updateMemoryRecording();
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  let updated = 0;
-  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
-
-  ok((yield waitUntil(() => updated > 10)),
-    "The overview graph was updated a bunch of times.");
-  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
-    "There are some markers available.");
-  ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
-    "There are some memory measurements available now.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  let interval = TimelineController.getInterval();
-  let markers = TimelineController.getMarkers();
-  let selection = TimelineView.markersOverview.getSelection();
-
-  is((selection.start) | 0,
-     (markers[0].start * TimelineView.markersOverview.dataScaleX) | 0,
-    "The initial selection start is correct.");
-
-  is((selection.end - selection.start) | 0,
-     (selectionRatio * TimelineView.markersOverview.width) | 0,
-    "The initial selection end is correct.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-02.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the overview has no initial selection when recording has finished
- * and there is no data available.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
-  let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
-
-  $("#memory-checkbox").checked = true;
-  yield TimelineController.updateMemoryRecording();
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  yield TimelineController._stopRecordingAndDiscardData();
-  ok(true, "Recording has ended.");
-
-  let markers = TimelineController.getMarkers();
-  let memory = TimelineController.getMemory();
-  let selection = TimelineView.markersOverview.getSelection();
-
-  is(markers.length, 0,
-    "There are no markers available.");
-  is(memory.length, 0,
-    "There are no memory measurements available.");
-  is(selection.start, null,
-    "The initial selection start is correct.");
-  is(selection.end, null,
-    "The initial selection end is correct.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_overview-theme.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the markers and memory overviews render with the correct
- * theme on load, and rerenders when changed.
- */
-
-const LIGHT_BG = "#fcfcfc";
-const DARK_BG = "#14171a";
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel("about:blank");
-  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
-
-  $("#memory-checkbox").checked = true;
-
-  setTheme("dark");
-
-  yield TimelineController.updateMemoryRecording();
-  is(TimelineView.markersOverview.backgroundColor, DARK_BG,
-    "correct theme on load for markers.");
-  is(TimelineView.memoryOverview.backgroundColor, DARK_BG,
-    "correct theme on load for memory.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  let refreshed = Promise.all([
-    once(TimelineView.markersOverview, "refresh"),
-    once(TimelineView.memoryOverview, "refresh"),
-  ]);
-
-  setTheme("light");
-  yield refreshed;
-
-  ok(true, "Both memory and markers were rerendered after theme change.");
-  is(TimelineView.markersOverview.backgroundColor, LIGHT_BG,
-    "correct theme on after toggle for markers.");
-  is(TimelineView.memoryOverview.backgroundColor, LIGHT_BG,
-    "correct theme on after toggle for memory.");
-
-  refreshed = Promise.all([
-    once(TimelineView.markersOverview, "refresh"),
-    once(TimelineView.memoryOverview, "refresh"),
-  ]);
-
-  setTheme("dark");
-  yield refreshed;
-
-  ok(true, "Both memory and markers were rerendered after theme change.");
-  is(TimelineView.markersOverview.backgroundColor, DARK_BG,
-    "correct theme on after toggle for markers once more.");
-  is(TimelineView.memoryOverview.backgroundColor, DARK_BG,
-    "correct theme on after toggle for memory once more.");
-
-  refreshed = Promise.all([
-    once(TimelineView.markersOverview, "refresh"),
-    once(TimelineView.memoryOverview, "refresh"),
-  ]);
-
-  // Set theme back to light
-  setTheme("light");
-  yield refreshed;
-});
-
-/**
- * Mimics selecting the theme selector in the toolbox;
- * sets the preference and emits an event on gDevTools to trigger
- * the themeing.
- */
-function setTheme (newTheme) {
-  let oldTheme = Services.prefs.getCharPref("devtools.theme");
-  info("Setting `devtools.theme` to \"" + newTheme + "\"");
-  Services.prefs.setCharPref("devtools.theme", newTheme);
-  gDevTools.emit("pref-changed", {
-    pref: "devtools.theme",
-    newValue: newTheme,
-    oldValue: oldTheme
-  });
-}
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_overview-update.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the markers and memory overviews are continuously updated.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel("about:blank");
-  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
-
-  $("#memory-checkbox").checked = true;
-  yield TimelineController.updateMemoryRecording();
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  ok("selectionEnabled" in TimelineView.markersOverview,
-    "The selection should not be enabled for the markers overview (1).");
-  is(TimelineView.markersOverview.selectionEnabled, false,
-    "The selection should not be enabled for the markers overview (2).");
-  is(TimelineView.markersOverview.hasSelection(), false,
-    "The markers overview shouldn't have a selection before recording.");
-
-  ok("selectionEnabled" in TimelineView.memoryOverview,
-    "The selection should not be enabled for the memory overview (1).");
-  is(TimelineView.memoryOverview.selectionEnabled, false,
-    "The selection should not be enabled for the memory overview (2).");
-  is(TimelineView.memoryOverview.hasSelection(), false,
-    "The memory overview shouldn't have a selection before recording.");
-
-  let updated = 0;
-  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
-
-  ok((yield waitUntil(() => updated > 10)),
-    "The overviews were updated a bunch of times.");
-  ok((yield waitUntil(() => TimelineController.getMemory().length > 10)),
-    "There are some memory measurements available now.");
-
-  ok("selectionEnabled" in TimelineView.markersOverview,
-    "The selection should still not be enabled for the markers overview (3).");
-  is(TimelineView.markersOverview.selectionEnabled, false,
-    "The selection should still not be enabled for the markers overview (4).");
-  is(TimelineView.markersOverview.hasSelection(), false,
-    "The markers overview should not have a selection while recording.");
-
-  ok("selectionEnabled" in TimelineView.memoryOverview,
-    "The selection should still not be enabled for the memory overview (3).");
-  is(TimelineView.memoryOverview.selectionEnabled, false,
-    "The selection should still not be enabled for the memory overview (4).");
-  is(TimelineView.memoryOverview.hasSelection(), false,
-    "The memory overview should not have a selection while recording.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  // TODO: Re-enable this assertion as part of bug 1120830
-  // is(TimelineController.getMarkers().length, 0,
-  //  "There are no markers available.");
-  isnot(TimelineController.getMemory().length, 0,
-    "There are some memory measurements available.");
-
-  is(TimelineView.markersOverview.selectionEnabled, true,
-    "The selection should now be enabled for the markers overview.");
-  // TODO: Re-enable this assertion as part of bug 1120830
-  // is(TimelineView.markersOverview.hasSelection(), false,
-  //  "The markers overview should not have a selection after recording.");
-
-  is(TimelineView.memoryOverview.selectionEnabled, true,
-    "The selection should now be enabled for the memory overview.");
-  // TODO: Re-enable this assertion as part of bug 1120830
-  // is(TimelineView.memoryOverview.hasSelection(), false,
-  //  "The memory overview should not have a selection after recording.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_panels.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the timeline panels are correctly shown and hidden when
- * recording starts and stops.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, EVENTS } = panel.panelWin;
-
-  is($("#record-button").hasAttribute("checked"), false,
-    "The record button should not be checked yet.");
-  is($("#timeline-pane").selectedPanel, $("#empty-notice"),
-    "An empty notice is initially displayed instead of the waterfall view.");
-
-  let whenRecStarted = panel.panelWin.once(EVENTS.RECORDING_STARTED);
-  EventUtils.synthesizeMouseAtCenter($("#record-button"), {}, panel.panelWin);
-  yield whenRecStarted;
-
-  ok(true, "Recording has started.");
-
-  is($("#record-button").getAttribute("checked"), "true",
-    "The record button should be checked now.");
-  is($("#timeline-pane").selectedPanel, $("#recording-notice"),
-    "A recording notice is now displayed instead of the waterfall view.");
-
-  let whenRecEnded = panel.panelWin.once(EVENTS.RECORDING_ENDED);
-  EventUtils.synthesizeMouseAtCenter($("#record-button"), {}, panel.panelWin);
-  yield whenRecEnded;
-
-  ok(true, "Recording has ended.");
-
-  is($("#record-button").hasAttribute("checked"), false,
-    "The record button should be unchecked again.");
-  is($("#timeline-pane").selectedPanel, $("#timeline-waterfall-container"),
-    "A waterfall view is now displayed.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_recording-without-memory.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the timeline actor isn't unnecessarily asked to record memory.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  let updated = 0;
-  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
-
-  ok((yield waitUntil(() => updated > 10)),
-    "The overview graph was updated a bunch of times.");
-  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
-    "There are some markers available.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  let markers = TimelineController.getMarkers();
-  let memory = TimelineController.getMemory();
-
-  isnot(markers.length, 0,
-    "There are some markers available.");
-  is(memory.length, 0,
-    "There are no memory measurements available.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_recording.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the timeline can properly start and stop a recording.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, gFront, TimelineController } = panel.panelWin;
-
-  $("#memory-checkbox").checked = true;
-  yield TimelineController.updateMemoryRecording();
-
-  is((yield gFront.isRecording()), false,
-    "The timeline actor should not be recording when the tool starts.");
-  is(TimelineController.getMarkers().length, 0,
-    "There should be no markers available when the tool starts.");
-  is(TimelineController.getMemory().length, 0,
-    "There should be no memory measurements available when the tool starts.");
-
-  yield TimelineController.toggleRecording();
-
-  is((yield gFront.isRecording()), true,
-    "The timeline actor should be recording now.");
-  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
-    "There are some markers available now.");
-  ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
-    "There are some memory measurements available now.");
-
-  info("Interval: " + TimelineController.getInterval().toSource());
-  info("Markers: " + TimelineController.getMarkers().toSource());
-  info("Memory: " + TimelineController.getMemory().toSource());
-
-  ok("startTime" in TimelineController.getInterval(),
-    "A `startTime` field was set on the recording data.");
-  ok("endTime" in TimelineController.getInterval(),
-    "An `endTime` field was set on the recording data.");
-
-  ok(TimelineController.getInterval().endTime >
-     TimelineController.getInterval().startTime,
-    "Some time has passed since the recording started.");
-
-  yield TimelineController.toggleRecording();
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-background.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the waterfall background is a 1px high canvas stretching across
- * the container bounds.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  let updated = 0;
-  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
-
-  ok((yield waitUntil(() => updated > 0)),
-    "The overview graphs were updated a bunch of times.");
-  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
-    "There are some markers available.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  // Test the waterfall background.
-
-  let parentWidth = $("#timeline-waterfall").getBoundingClientRect().width;
-  let waterfallWidth = TimelineView.waterfall._waterfallWidth;
-  let sidebarWidth = 150; // px
-  is(waterfallWidth, parentWidth - sidebarWidth,
-    "The waterfall width is correct.")
-
-  ok(TimelineView.waterfall._canvas,
-    "A canvas should be created after the recording ended.");
-  ok(TimelineView.waterfall._ctx,
-    "A 2d context should be created after the recording ended.");
-
-  is(TimelineView.waterfall._canvas.width, waterfallWidth,
-    "The canvas width is correct.");
-  is(TimelineView.waterfall._canvas.height, 1,
-    "The canvas height is correct.");
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-sidebar.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the sidebar is properly updated when a marker is selected.
- */
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { $, $$, EVENTS, TimelineController, TimelineView, TIMELINE_BLUEPRINT} = panel.panelWin;
-  let { L10N } = devtools.require("devtools/shared/timeline/global");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  yield waitUntil(() => {
-    // Wait until we get 3 different markers.
-    let markers = TimelineController.getMarkers();
-    return markers.some(m => m.name == "Styles") &&
-           markers.some(m => m.name == "Reflow") &&
-           markers.some(m => m.name == "Paint");
-  });
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  // Select everything
-  TimelineView.markersOverview.setSelection({ start: 0, end: TimelineView.markersOverview.width })
-
-
-  let bars = $$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar");
-  let markers = TimelineController.getMarkers();
-
-  ok(bars.length > 2, "got at least 3 markers");
-
-  let sidebar = $("#timeline-waterfall-details");
-  for (let i = 0; i < bars.length; i++) {
-    let bar = bars[i];
-    bar.click();
-    let m = markers[i];
-
-    let name = TIMELINE_BLUEPRINT[m.name].label;
-
-    is($("#timeline-waterfall-details .marker-details-type").getAttribute("value"), name,
-      "sidebar title matches markers name");
-
-    let printedStartTime = $(".marker-details-start .marker-details-labelvalue").getAttribute("value");
-    let printedEndTime = $(".marker-details-end .marker-details-labelvalue").getAttribute("value");
-    let printedDuration= $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
-
-    let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
-
-    // Values are rounded. We don't use a strict equality.
-    is(toMs(m.start), printedStartTime, "sidebar start time is valid");
-    is(toMs(m.end), printedEndTime, "sidebar end time is valid");
-    is(toMs(m.end - m.start), printedDuration, "sidebar duration is valid");
-  }
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-styles.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests if the waterfall is properly built after making a selection
- * and the child nodes are styled correctly.
- */
-
-var gRGB_TO_HSL = {
- "rgb(193, 132, 214)": "hsl(285,50%,68%)",
- "rgb(152, 61, 183)": "hsl(285,50%,48%)",
- "rgb(161, 223, 138)": "hsl(104,57%,71%)",
- "rgb(96, 201, 58)": "hsl(104,57%,51%)",
- "rgb(240, 195, 111)": "hsl(39,82%,69%)",
- "rgb(227, 155, 22)": "hsl(39,82%,49%)",
- "rgb(204, 204, 204)": "hsl(0,0%,80%)",
- "rgb(153, 153, 153)": "hsl(0,0%,60%)",
-};
-
-add_task(function*() {
-  let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
-  let { TIMELINE_BLUEPRINT } = devtools.require("devtools/shared/timeline/global");
-  let { $, $$, EVENTS, TimelineController } = panel.panelWin;
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has started.");
-
-  let updated = 0;
-  panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
-
-  ok((yield waitUntil(() => updated > 0)),
-    "The overview graphs were updated a bunch of times.");
-  ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
-    "There are some markers available.");
-
-  yield TimelineController.toggleRecording();
-  ok(true, "Recording has ended.");
-
-  // Test the table sidebars.
-
-  for (let sidebar of [
-    ...$$(".timeline-header-sidebar"),
-    ...$$(".timeline-marker-sidebar")
-  ]) {
-    is(sidebar.getAttribute("width"), "150",
-      "The table's sidebar width is correct.");
-  }
-
-  // Test the table ticks.
-
-  for (let tick of $$(".timeline-header-tick")) {
-    ok(tick.getAttribute("value").match(/^\d+ ms$/),
-      "The table's timeline ticks appear to have correct labels.");
-    ok(tick.style.transform.match(/^translateX\(.*px\)$/),
-      "The table's timeline ticks appear to have proper translations.");
-  }
-
-  // Test the marker bullets.
-
-  for (let bullet of $$(".timeline-marker-bullet")) {
-    let type = bullet.getAttribute("type");
-
-    ok(type in TIMELINE_BLUEPRINT,
-      "The bullet type is present in the timeline blueprint.");
-    is(gRGB_TO_HSL[bullet.style.backgroundColor], TIMELINE_BLUEPRINT[type].fill,
-      "The bullet's background color is correct.");
-    is(gRGB_TO_HSL[bullet.style.borderColor], TIMELINE_BLUEPRINT[type].stroke,
-      "The bullet's border color is correct.");
-  }
-
-  // Test the marker bars.
-
-  for (let bar of $$(".timeline-marker-bar")) {
-    let type = bar.getAttribute("type");
-
-    ok(type in TIMELINE_BLUEPRINT,
-      "The bar type is present in the timeline blueprint.");
-    is(gRGB_TO_HSL[bar.style.backgroundColor], TIMELINE_BLUEPRINT[type].fill,
-      "The bar's background color is correct.");
-    is(gRGB_TO_HSL[bar.style.borderColor], TIMELINE_BLUEPRINT[type].stroke,
-      "The bar's border color is correct.");
-
-    ok(bar.getAttribute("width") > 0,
-      "The bar appears to have a proper width.");
-    ok(bar.style.transform.match(/^translateX\(.*px\)$/),
-      "The bar appears to have proper translations.");
-  }
-});
deleted file mode 100644
--- a/browser/devtools/timeline/test/doc_simple-test.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!-- Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/ -->
-<!doctype html>
-
-<html>
-  <head>
-    <meta charset="utf-8"/>
-    <title>Timeline test page</title>
-  </head>
-
-  <body>
-    <script type="text/javascript">
-      var x = 1;
-      function test() {
-        document.body.style.borderTop = x + "px solid red";
-        x = 1^x;
-        document.body.innerHeight; // flush pending reflows
-      }
-
-      // Prevent this script from being garbage collected.
-      window.setInterval(test, 1);
-    </script>
-  </body>
-
-</html>
deleted file mode 100644
--- a/browser/devtools/timeline/test/head.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-
-// Disable logging for all the tests. Both the debugger server and frontend will
-// be affected by this pref.
-let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
-Services.prefs.setBoolPref("devtools.debugger.log", false);
-
-// Enable the tool while testing.
-let gToolEnabled = Services.prefs.getBoolPref("devtools.timeline.enabled");
-Services.prefs.setBoolPref("devtools.timeline.enabled", true);
-
-let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
-let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
-let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
-let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-
-let TargetFactory = devtools.TargetFactory;
-let Toolbox = devtools.Toolbox;
-
-const EXAMPLE_URL = "http://example.com/browser/browser/devtools/timeline/test/";
-const SIMPLE_URL = EXAMPLE_URL + "doc_simple-test.html";
-
-// All tests are asynchronous.
-waitForExplicitFinish();
-
-registerCleanupFunction(() => {
-  info("finish() was called, cleaning up...");
-  Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
-  Services.prefs.setBoolPref("devtools.timeline.enabled", gToolEnabled);
-});
-
-// Close the toolbox and all opened tabs automatically.
-registerCleanupFunction(function*() {
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  yield gDevTools.closeToolbox(target);
-
-  while (gBrowser.tabs.length > 1) {
-    gBrowser.removeCurrentTab();
-  }
-});
-
-function addTab(url) {
-  info("Adding tab: " + url);
-
-  let deferred = promise.defer();
-  let tab = gBrowser.selectedTab = gBrowser.addTab(url);
-  let linkedBrowser = tab.linkedBrowser;
-
-  linkedBrowser.addEventListener("load", function onLoad() {
-    linkedBrowser.removeEventListener("load", onLoad, true);
-    info("Tab added and finished loading: " + url);
-    deferred.resolve(tab);
-  }, true);
-
-  return deferred.promise;
-}
-
-/**
- * Spawns a new tab and starts up a toolbox with the timeline panel
- * automatically selected.
- *
- * Must be used within a task.
- *
- * @param string url
- *        The location of the new tab to spawn.
- * @return object
- *         A promise resolved once the timeline is initialized, with the
- *         {target, panel} instances.
- */
-function* initTimelinePanel(url) {
-  info("Initializing a timeline pane.");
-
-  let tab = yield addTab(url);
-  let target = TargetFactory.forTab(tab);
-
-  yield target.makeRemote();
-
-  let toolbox = yield gDevTools.showToolbox(target, "timeline");
-  let panel = toolbox.getCurrentPanel();
-  return { target, panel };
-}
-
-/**
- * Waits until a predicate returns true.
- *
- * @param function predicate
- *        Invoked once in a while until it returns true.
- * @param number interval [optional]
- *        How often the predicate is invoked, in milliseconds.
- */
-function waitUntil(predicate, interval = 10) {
-  if (predicate()) {
-    return promise.resolve(true);
-  }
-  let deferred = promise.defer();
-  setTimeout(function() {
-    waitUntil(predicate).then(() => deferred.resolve(true));
-  }, interval);
-  return deferred.promise;
-
-}
-
-/**
- * Wait until next tick.
- */
-function nextTick() {
-  let def = promise.defer();
-  executeSoon(() => def.resolve())
-  return def.promise;
-}
-
-/**
- * Wait for eventName on target.
- * @param {Object} target An observable object that either supports on/off or
- * addEventListener/removeEventListener
- * @param {String} eventName
- * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
- * @return A promise that resolves when the event has been handled
- */
-function once(target, eventName, useCapture=false) {
-  info("Waiting for event: '" + eventName + "' on " + target + ".");
-
-  let deferred = promise.defer();
-
-  for (let [add, remove] of [
-    ["addEventListener", "removeEventListener"],
-    ["addListener", "removeListener"],
-    ["on", "off"]
-  ]) {
-    if ((add in target) && (remove in target)) {
-      target[add](eventName, function onEvent(...aArgs) {
-        info("Got event: '" + eventName + "' on " + target + ".");
-        target[remove](eventName, onEvent, useCapture);
-        deferred.resolve.apply(deferred, aArgs);
-      }, useCapture);
-      break;
-    }
-  }
-
-  return deferred.promise;
-}
--- a/browser/devtools/webide/modules/simulator-process.js
+++ b/browser/devtools/webide/modules/simulator-process.js
@@ -66,19 +66,21 @@ SimulatorProcess.prototype = {
     });
 
     this.on("stdout", (e, data) => this.log(e, data.trim()));
     this.on("stderr", (e, data) => this.log(e, data.trim()));
 
     let environment;
     if (OS.indexOf("linux") > -1) {
       environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
-      if ("DISPLAY" in Environment) {
-        environment.push("DISPLAY=" + Environment.DISPLAY);
-      }
+      ["DISPLAY", "XAUTHORITY"].forEach(key => {
+        if (key in Environment) {
+          environment.push(key + "=" + Environment[key]);
+        }
+      });
     }
 
     // Spawn a B2G instance.
     this.process = Subprocess.call({
       command: b2g,
       arguments: this.args,
       environment: environment,
       stdout: data => this.emit("stdout", data),
new file mode 100644
--- /dev/null
+++ b/browser/docs/DirectoryLinksProvider.rst
@@ -0,0 +1,248 @@
+=============================================
+Directory Links Architecture and Data Formats
+=============================================
+
+Directory links are enhancements to the new tab experience that combine content
+Firefox already knows about from user browsing with external content. There are
+3 kinds of links:
+
+- directory links fill in additional tiles on the new tab page if there would
+  have been empty tiles because the user has a clean profile or cleared history
+- suggested links are shown if certain triggering criteria matches the user's
+  browsing behavior, i.e., if the user has a top site that matches one of
+  several possible sites. E.g., only show a sports suggestion if the user has a
+  sport site as a top site
+- enhanced links replace a matching user's visible history tile from the same
+  site but only the visual aspects: title, image, and rollover image
+
+To power the above features, DirectoryLinksProvider module downloads, at most
+once per 24 hours, the directory source links as JSON with enough data for
+Firefox to determine what should be shown or not. This module also handles
+reporting back data about the tiles via asynchronous pings that don't return
+data from the server.
+
+For the directory source and ping endpoints, the default preference values point
+to Mozilla key-pinned servers with encryption. No cookies are set by the servers
+but not enforced by Firefox.
+
+- default directory source endpoint:
+  https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%
+- default directory ping endpoint: https://tiles.services.mozilla.com/v3/links/
+
+
+Preferences
+===========
+
+There are two main preferences that control downloading links and reporting
+metrics.
+
+``browser.newtabpage.directory.source``
+---------------------------------------
+
+This endpoint tells Firefox where to download directory source file as a GET
+request. It should return JSON of the appropriate format containing the relevant
+links data. The value can be a data URI, e.g., an empty JSON object effectively
+turns off remote downloading: ``data:text/plain,{}``
+
+The preference value will have %LOCALE% and %CHANNEL% replaced by the
+appropriate values for the build of Firefox, e.g.,
+
+- directory source endpoint:
+  https://tiles.services.mozilla.com/v3/links/fetch/en-US/release
+
+``browser.newtabpage.directory.ping``
+-------------------------------------
+
+This endpoint tells Firefox where to report Tiles metrics as a POST request. The
+data is sent as a JSON blob. Setting it to empty effectively turns off reporting
+of Tiles data.
+
+A path segment will be appended to the endpoint of "view" or "click" depending
+on the type of ping, e.g.,
+
+- ``view`` ping endpoint: https://tiles.services.mozilla.com/v3/links/view
+- ``click`` ping endpoint: https://tiles.services.mozilla.com/v3/links/click
+
+
+Data Flow
+=========
+
+When Firefox starts, it checks for a cached directory source file. If one
+exists, it checks for its timestamp to determine if a new file should be
+downloaded.
+
+If a directory source file needs to be downloaded, a GET request is made then
+cacheed and unpacked the JSON into the different types of links. Various checks
+filter out invalid links, e.g., those with http-hosted images or those that
+don't fit the allowed suggestions.
+
+When a new tab page is built, DirectoryLinksProvider module provides additional
+link data that is combined with history link data to determine which links can
+be displayed or not.
+
+When a new tab page is shown, a ``view`` ping is sent with relevant tiles data.
+Similarly, when the user clicks on various parts of tiles (to load the page,
+pin, block, etc.), a ``click`` ping is sent with similar data. Both of these can
+trigger downloading of fresh directory source links if 24 hours have elapsed
+since last download.
+
+Users can turn off the ping with in-new-tab-page controls.
+
+As the new tab page is rendered, any images for tiles are downloaded if not
+already cached. The default servers hosting the images are Mozilla CDN that
+don't use cookies: https://tiles.cdn.mozilla.net/
+
+
+Source JSON Format
+==================
+
+Firefox expects links data in a JSON object with top level keys each providing
+an array of tile objects. The keys correspond to the different types of links:
+``directory``, ``suggested``, and ``enhanced``.
+
+Example
+-------
+
+Below is an example directory source file::
+
+  {
+      "directory": [
+          {
+              "bgColor": "",
+              "directoryId": 498,
+              "enhancedImageURI": "https://tiles.cdn.mozilla.net/images/d11ba0b3095bb19d8092cd29be9cbb9e197671ea.28088.png",
+              "imageURI": "https://tiles.cdn.mozilla.net/images/1332a68badf11e3f7f69bf7364e79c0a7e2753bc.5316.png",
+              "title": "Mozilla Community",
+              "type": "affiliate",
+              "url": "http://contribute.mozilla.org/"
+          }
+      ],
+      "enhanced": [
+          {
+              "bgColor": "",
+              "directoryId": 776,
+              "enhancedImageURI": "https://tiles.cdn.mozilla.net/images/44a14fc405cebc299ead86514dff0e3735c8cf65.10814.png",
+              "imageURI": "https://tiles.cdn.mozilla.net/images/20e24aa2219ec7542cc8cf0fd79f0c81e16ebeac.11859.png",
+              "title": "TurboTax",
+              "type": "sponsored",
+              "url": "https://turbotax.intuit.com/"
+          }
+      ],
+      "suggested": [
+          {
+              "bgColor": "#cae1f4",
+              "directoryId": 702,
+              "frecent_sites": [
+                  "addons.mozilla.org",
+                  "air.mozilla.org",
+                  "blog.mozilla.org",
+                  "bugzilla.mozilla.org",
+                  "developer.mozilla.org",
+                  "etherpad.mozilla.org",
+                  "hacks.mozilla.org",
+                  "hg.mozilla.org",
+                  "mozilla.org",
+                  "planet.mozilla.org",
+                  "quality.mozilla.org",
+                  "support.mozilla.org",
+                  "treeherder.mozilla.org",
+                  "wiki.mozilla.org"
+              ],
+              "imageURI": "https://tiles.cdn.mozilla.net/images/9ee2b265678f2775de2e4bf680df600b502e6038.3875.png",
+              "title": "Thanks for testing!",
+              "type": "affiliate",
+              "url": "https://www.mozilla.com/firefox/tiles"
+          }
+      ]
+  }
+
+Link Object
+-----------
+
+Each link object has various values that Firefox uses to display a tile:
+
+- ``url`` - string url for the page to be loaded when the tile is clicked. Only
+  https and http URLs are allowed.
+- ``title`` - string that appears below the tile.
+- ``type`` - string relationship of the link to Mozilla. Expected values:
+  affiliate, organic, sponsored.
+- ``imageURI`` - string url for the tile image to show. Only https and data URIs
+  are allowed.
+- ``enhancedImageURI`` - string url for the image to be shown before the user
+  hovers. Only https and data URIs are allowed.
+- ``bgColor`` - string css color for additional fill background color.
+- ``directoryId`` - id of the tile to be used during ping reporting
+
+Suggested Link Object Extras
+----------------------------
+
+A suggested link has additional values:
+
+- ``frecent_sites`` - array of strings of the sites that can trigger showing a
+  Suggested Tile if the user has the site in one of the top 100 most-frecent
+  pages. Only preapproved array of strings that are hardcoded into the
+  DirectoryLinksProvider module are allowed.
+
+The preapproved arrays follow a policy for determining what topic grouping is
+allowed as well as the composition of a grouping. The topics are broad
+uncontroversial categories, e.g., Mobile Phone, News, Technology, Video Game,
+Web Development. There are at least 5 sites within a grouping, and as many
+popular sites relevant to the topic are included to avoid having one site be
+clearly dominant. These requirements provide some deniability of which site
+actually triggered a suggestion during ping reporting, so it's more difficult to
+determine if a user has gone to a specific site.
+
+
+Ping JSON Format
+================
+
+Firefox reports back an action and the state of tiles on the new tab page based
+on the user opening a new tab or clicking a tile. The top level keys of the
+ping:
+
+- ``locale`` - string locale of the Firefox build
+- ``tiles`` - array of tiles ping objects
+
+An additional key at the top level indicates which action triggered the ping.
+The value associated to the action key is the 0-based index into the tiles array
+of which tile triggered the action. Valid actions: block, click, pin, sponsored,
+sponsored_link, unpin, view. E.g., if the second tile is being clicked, the ping
+will have ``"click": 1``
+
+Example
+-------
+
+Below is an example ``click`` ping with 3 tiles: a pinned suggested tile
+followed by a history tile and a directory tile. The first tile is being
+blocked::
+
+  {
+      "locale": "en-US",
+      "tiles": [
+          {
+              "id": 702,
+              "pin": 1,
+          },
+          {},
+          {
+              "id": 498,
+          }
+      ],
+      "block": 0
+  }
+
+Tiles Ping Object
+-----------------
+
+Each tile of the new tab page is reported back as part of the ping with some or
+none of the following optional values:
+
+- ``id`` - id that was provided as part of the downloaded link object (for all
+  types of links: directory, suggested, enhanced); not present if the tile was
+  created from user behavior, e.g., visiting pages
+- ``pinned`` - 1 if the tile is pinned; not present otherwise
+- ``pos`` - integer position if the tile is not in the natural order, e.g., a
+  pinned tile after an empty slot; not present otherwise
+- ``score`` - integer truncated score based on the tile's frecency; not present
+  if 0
+- ``url`` - empty string if it's an enhanced tile; not present otherwise
--- a/browser/docs/index.rst
+++ b/browser/docs/index.rst
@@ -2,9 +2,10 @@
 Firefox
 =======
 
 This is the nascent documentation of the Firefox front-end code.
 
 .. toctree::
    :maxdepth: 1
 
+   DirectoryLinksProvider
    UITelemetry
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -103,17 +103,17 @@ add_contact_button=Add Contact
 ### LOCALIZATION NOTE (valid_email_text_description): This is displayed when
 ### the user enters an invalid email address, preventing the addition of the
 ### contact.
 valid_email_text_description=Please enter a valid email address
 
 ## LOCALIZATION NOTE (add_or_import_contact_title): This is the subtitle of the
 ## panel.
 add_or_import_contact_title=Add or Import Contact
-import_contacts_button=Import
+import_contacts_button2=Import from Google
 importing_contacts_progress_button=Importing…
 import_contacts_failure_message=Some contacts could not be imported. Please try again.
 ## LOCALIZATION NOTE(import_contacts_success_message): Success notification message
 ## when user's contacts have been successfully imported.
 ## Semicolon-separated list of plural forms. See:
 ## http://developer.mozilla.org/en/docs/Localization_and_Plurals
 ## In this item, don't translate the part between {{..}}
 import_contacts_success_message={{total}} contact was successfully imported.;{{total}} contacts were successfully imported.
@@ -122,19 +122,19 @@ import_contacts_success_message={{total}
 sync_contacts_button=Sync Contacts
 
 ## LOCALIZATION NOTE(import_failed_description simple): Displayed when an import of
 ## contacts fails. This is displayed in the error field.
 import_failed_description_simple=Sorry, contact import failed
 import_failed_description_some=Some contacts could not be imported
 import_failed_support_button=Help
 
-## LOCALIZATION NOTE(remove_contact_menu_button): Displayed in the contact list in
+## LOCALIZATION NOTE(remove_contact_menu_button2): Displayed in the contact list in
 ## a pop-up menu next to the contact's name.
-remove_contact_menu_button=Remove Contact
+remove_contact_menu_button2=Remove Contact…
 ## LOCALIZATION NOTE(confirm_delete_contact_alert): This is an alert that is displayed
 ## to confirm deletion of a contact.
 confirm_delete_contact_alert=Are you sure you want to delete this contact?
 ## LOCALIZATION NOTE(confirm_delete_contact_remove_button, confirm_delete_contact_cancel_button):
 ## These are displayed on the alert with confirm_delete_contact_alert
 confirm_delete_contact_remove_button=Remove Contact
 confirm_delete_contact_cancel_button=Cancel
 
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -45,16 +45,44 @@ const PREF_SELECTED_LOCALE = "general.us
 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
 
 // The preference that tells where to send click/view pings
 const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
 // The preference that tells if newtab is enhanced
 const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
+// Only allow explicitly approved frecent sites with display name
+const ALLOWED_FRECENT_SITES = new Map([
+  [ 'airdroid.com,android-developers.blogspot.com,android.com,androidandme.com,androidapplications.com,androidapps.com,androidauthority.com,androidcentral.com,androidcommunity.com,androidfilehost.com,androidforums.com,androidguys.com,androidheadlines.com,androidpit.com,androidpolice.com,androidspin.com,androidtapp.com,androinica.com,droid-life.com,droidforums.net,droidviews.com,droidxforums.com,forum.xda-developers.com,phandroid.com,play.google.com,shopandroid.com,talkandroid.com,theandroidsoul.com,thedroidguy.com,videodroid.org',
+    'Technology' ],
+  [ 'assurancewireless.com,att.com,attsavings.com,boostmobile.com,budgetmobile.com,consumercellular.com,credomobile.com,gosmartmobile.com,h2owirelessnow.com,lycamobile.com,lycamobile.us,metropcs.com,myfamilymobile.com,polarmobile.com,qlinkwireless.com,republicwireless.com,sprint.com,straighttalk.com,t-mobile.com,tracfonewireless.com,verizonwireless.com,virginmobile.com,virginmobile.com.au,virginmobileusa.com,vodafone.co.uk,vodafone.com,vzwshop.com',
+    'Mobile Phone' ],
+  [ 'addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org',
+    'Mozilla' ],
+  [ '3dprint.com,4sysops.com,access-programmers.co.uk,accountingweb.com,addictivetips.com,adweek.com,afterdawn.com,akihabaranews.com,anandtech.com,appsrumors.com,arstechnica.com,belkin.com,besttechinfo.com,betanews.com,bgr.com,botcrawl.com,breakingmuscle.com,canonrumors.com,cheap-phones.com,chip.de,chip.eu,cio.com,citeworld.com,cleanpcremove.com,cnet.com,commentcamarche.net,computer.org,computerhope.com,computershopper.com,computerweekly.com,contextures.com,coolest-gadgets.com,crn.com,csoonline.com,daniweb.com,data.com,datacenterknowledge.com,ddj.com,devicemag.com,digitaltrends.com,dottech.org,dpreview.com,dslreports.com,edugeek.net,eetimes.com,engadget.com,epic.com,eurekalert.org,eweek.com,experts-exchange.com,extremetech.com,fosshub.com,freesoftwaremagazine.com,funkyspacemonkey.com,futuremark.com,gadgetreview.com,ghacks.net,gizmodo.co.uk,gizmodo.com,globalsecurity.org,greenbot.com,gunup.com,guru3d.com,head-fi.org,hexus.net,hothardware.com,howtoforge.com,idg.com.au,idigitaltimes.com,idownloadblog.com,ihackmyi.com,ilounge.com,infomine.com,informationweek.com,intellireview.com,intomobile.com,iphonehacks.com,ismashphone.com,isource.com,it168.com,itechpost.com,itpro.co.uk,itworld.com,jailbreaknation.com,kioskea.net,laptoping.com,laptopmag.com,lightreading.com,livescience.com,malwaretips.com,mediaroom.com,mobilemag.com,modmyi.com,modmymobile.com,mophie.com,mozillazine.org,neoseeker.com,neowin.net,newscientist.com,newsoxy.com,nextadvisor.com,notebookcheck.com,notebookreview.com,nvidia.com,nwc.com,orafaq.com,osdir.com,osxdaily.com,our-hometown.com,pcadvisor.co.uk,pchome.net,pcmag.com,pconline.com.cn,pcpop.com,pcpro.co.uk,pcreview.co.uk,pcrisk.com,pcwelt.de,phonerebel.com,phonescoop.com,physorg.com,pocket-lint.com,post-theory.com,prnewswire.co.uk,prnewswire.com,programming4.us,quickpwn.com,readwrite.com,redmondpie.com,redorbit.com,reviewed.com,safer-networking.org,sciencedaily.com,sciencenews.org,scientificamerican.com,scientificblogging.com,sciverse.com,servicerow.com,sinfuliphone.com,singularityhub.com,slashdot.org,slashgear.com,softonic.com,softonic.com.br,softonic.fr,sophos.com,space.com,sparkfun.com,speedguide.net,stuff.tv,techdailynews.net,techdirt.com,techeblog.com,techhive.com,techie-buzz.com,technewsworld.com,techniqueworld.com,technobuffalo.com,technologyreview.com,technologytell.com,techpowerup.com,techpp.com,techrepublic.com,techshout.com,techweb.com,techworld.com,techworld.com.au,techworldtweets.com,telecomfile.com,tgdaily.com,theinquirer.net,thenextweb.com,theregister.co.uk,thermofisher.com,theverge.com,thewindowsclub.com,tomsguide.com,tomshardware.com,tomsitpro.com,toptenreviews.com,trustedreviews.com,tuaw.com,tweaktown.com,ubergizmo.com,unwiredview.com,venturebeat.com,wccftech.com,webmonkey.com,webpronews.com,windows7codecs.com,windowscentral.com,windowsitpro.com,windowstechies.com,winsupersite.com,wired.co.uk,wired.com,wp-themes.com,xda-developers.com,xml.com,zdnet.com,zmescience.com,zol.com.cn',
+    'Technology' ],
+  [ '9to5mac.com,appadvice.com,apple.com,appleinsider.com,appleturns.com,appsafari.com,cultofmac.com,everymac.com,insanelymac.com,iphoneunlockspot.com,isource.com,itunes.apple.com,lowendmac.com,mac-forums.com,macdailynews.com,macenstein.com,macgasm.net,macintouch.com,maclife.com,macnews.com,macnn.com,macobserver.com,macosx.com,macpaw.com,macrumors.com,macsales.com,macstories.net,macupdate.com,macuser.co.uk,macworld.co.uk,macworld.com,maxiapple.com,spymac.com,theapplelounge.com',
+    'Technology' ],
+  [ 'alistapart.com,answers.microsoft.com,backpack.openbadges.org,blog.chromium.org,caniuse.com,codefirefox.com,codepen.io,css-tricks.com,css3generator.com,cssdeck.com,csswizardry.com,devdocs.io,docs.angularjs.org,ghacks.net,github.com,html5demos.com,html5rocks.com,html5test.com,iojs.org,khanacademy.org,l10n.mozilla.org,learn.jquery.com,marketplace.firefox.com,mozilla-hispano.org,mozillians.org,news.ycombinator.com,npmjs.com,packagecontrol.io,quirksmode.org,readwrite.com,reps.mozilla.org,smashingmagazine.com,stackoverflow.com,status.modern.ie,teamtreehouse.com,tutorialspoint.com,udacity.com,validator.w3.org,w3.org,w3cschool.cc,w3schools.com,whatcanidoformozilla.org',
+    'Web Development' ],
+  [ 'classroom.google.com,codecademy.com,elearning.ut.ac.id,khanacademy.org,learn.jquery.com,teamtreehouse.com,tutorialspoint.com,udacity.com,w3cschool.cc,w3schools.com',
+    'Web Education' ],
+  [ 'abebooks.co.uk,abebooks.com,alibris.com,allaboutcircuits.com,allyoucanbooks.com,answersingenesis.org,artnet.com,audiobooks.com,barnesandnoble.com,barnesandnobleinc.com,bartleby.com,betterworldbooks.com,biggerbooks.com,bncollege.com,bookbyte.com,bookdepository.com,bookfinder.com,bookrenter.com,booksamillion.com,booksite.com,boundless.com,brookstone.com,btol.com,calibre-ebook.com,campusbookrentals.com,casadellibro.com,cbomc.com,cengagebrain.com,chapters.indigo.ca,christianbook.com,ciscopress.com,coursesmart.com,cqpress.com,crafterschoice.com,crossings.com,cshlp.org,deseretbook.com,directtextbook.com,discountmags.com,doubledaybookclub.com,doubledaylargeprint.com,doverpublications.com,ebooks.com,ecampus.com,fellabooks.net,fictionwise.com,flatworldknowledge.com,grolier.com,harpercollins.com,hayhouse.com,historybookclub.com,hpb.com,hpbmarketplace.com,interweave.com,iseeme.com,katiekazoo.com,knetbooks.com,learnoutloud.com,librarything.com,literaryguild.com,lulu.com,lww.com,macmillan.com,magazines.com,mbsdirect.net,militarybookclub.com,mypearsonstore.com,mysteryguild.com,netplaces.com,noble.com,novelguide.com,onespirit.com,oxfordjournals.org,paperbackswap.com,papy.co.jp,peachpit.com,penguin.com,penguingroup.com,pimsleur.com,powells.com,qpb.com,quepublishing.com,reviews.com,rhapsodybookclub.com,rodalestore.com,royalsocietypublishing.org,sagepub.com,scrubsmag.com,sfbc.com,simonandschuster.com,simonandschuster.net,simpletruths.com,teach12.net,textbooks.com,textbookx.com,thegoodcook.com,thriftbooks.com,tlsbooks.com,toshibabookplace.com,tumblebooks.com,urbookdownload.com,valorebooks.com,valuemags.com,wwnorton.com,zoobooks.com',
+    'Literature' ],
+  [ 'aceshowbiz.com,aintitcoolnews.com,askkissy.com,askmen.com,atraf.co.il,audioboom.com,beamly.com,blippitt.com,bollywoodlife.com,bossip.com,buzzlamp.com,celebdirtylaundry.com,celebfocus.com,celebitchy.com,celebrity-gossip.net,celebrityabout.com,celebwild.com,chisms.net,concertboom.com,crushable.com,cultbox.co.uk,dailyentertainmentnews.com,dayscafe.com,deadline.com,deathandtaxesmag.com,diaryofahollywoodstreetking.com,digitalspy.com,egotastic.com,empirenews.net,enelbrasero.com,everydaycelebs.com,ew.com,extratv.com,facade.com,fanaru.com,fhm.com,geektyrant.com,glamourpage.com,heatworld.com,hlntv.com,hollyscoop.com,hollywoodreporter.com,hollywoodtuna.com,hypable.com,infotransfer.net,insideedition.com,interaksyon.com,jezebel.com,justjared.com,justjaredjr.com,komando.com,koreaboo.com,maxgo.com,maxim.com,maxviral.com,mediatakeout.com,mosthappy.com,moviestalk.com,my.ology.com,ngoisao.net,nofilmschool.com,nolocreo.com,octane.tv,ouchpress.com,people.com,peopleenespanol.com,perezhilton.com,pinkisthenewblog.com,platotv.tv,playbill.com,playbillvault.com,playgroundmag.net,popeater.com,popnhop.com,popsugar.co.uk,popsugar.com,purepeople.com,radaronline.com,rantchic.com,reshareworthy.com,rinkworks.com,ripbird.com,sara-freder.com,screenjunkies.com,soapcentral.com,soapoperadigest.com,sobadsogood.com,splitsider.com,starcasm.net,starpulse.com,straightfromthea.com,stupidcelebrities.net,stupiddope.com,tbn.org,theawesomedaily.com,theawl.com,thefrisky.com,thefw.com,theresacaputo.com,thezooom.com,tvnotas.com.mx,twanatells.com,vanswarpedtour.com,vietgiaitri.com,viral.buzz,vulture.com,wakavision.com,worthytales.net,wwtdd.com,younghollywood.com',
+    'Entertainment News' ],
+  [ '247wallst.com,4-traders.com,advfn.com,agweb.com,allbusiness.com,barchart.com,barrons.com,beckershospitalreview.com,benzinga.com,bizjournals.com,bizsugar.com,bloomberg.com,bloomberglaw.com,business-standard.com,businessinsider.com,businessinsider.com.au,businesspundit.com,businessweek.com,businesswire.com,cboe.com,cheatsheet.com,chicagobusiness.com,cjonline.com,cnbc.com,cnnmoney.com,cqrcengage.com,dailyfinance.com,dailyfx.com,dealbreaker.com,djindexes.com,dowjones.com,easierstreetdaily.com,economist.com,economyandmarkets.com,economywatch.com,edweek.org,eleconomista.es,entrepreneur.com,etfdailynews.com,etfdb.com,ewallstreeter.com,fastcolabs.com,fastcompany.com,financeformulas.net,financialpost.com,flife.de,forbes.com,forexpros.com,fortune.com,foxbusiness.com,ft.com,ftpress.com,fx-exchange.com,hbr.org,howdofinance.com,ibtimes.com,inc.com,investopedia.com,investors.com,investorwords.com,journalofaccountancy.com,kiplinger.com,lendingandcredit.net,lfb.org,mainstreet.com,markettraders.com,marketwatch.com,maxkeiser.com,minyanville.com,ml.com,moneycontrol.com,moneymappress.com,moneynews.com,moneysavingexpert.com,morningstar.com,mortgagenewsdaily.com,motleyfool.com,mt.co.kr,nber.org,nyse.com,oilprice.com,pewsocialtrends.org,principal.com,qz.com,rantfinance.com,realclearmarkets.com,recode.net,reuters.ca,reuters.co.in,reuters.co.uk,reuters.com,rttnews.com,seekingalpha.com,smallbiztrends.com,streetinsider.com,thecheapinvestor.com,theeconomiccollapseblog.com,themoneyconverter.com,thestreet.com,tickertech.com,tradingeconomics.com,updown.com,valuewalk.com,wikinvest.com,wsj.com,zacks.com',
+    'Financial News' ],
+  [ '10tv.com,8newsnow.com,9news.com,abc.net.au,abc7.com,abc7chicago.com,abcnews.go.com,aclu.org,activistpost.com,ajc.com,al.com,alan.com,alarab.net,aljazeera.com,americanthinker.com,app.com,aristeguinoticias.com,azcentral.com,baltimoresun.com,becomingminimalist.com,beforeitsnews.com,bigstory.ap.org,blackamericaweb.com,bloomberg.com,bloombergview.com,boston.com,bostonherald.com,breitbart.com,buffalonews.com,c-span.org,canada.com,cbs46.com,cbsnews.com,chicagotribune.com,chron.com,citizensvoice.com,citylab.com,cleveland.com,cnn.com,coed.com,countercurrentnews.com,courant.com,ctvnews.ca,dailyherald.com,dailynews.com,dallasnews.com,delawareonline.com,democratandchronicle.com,democraticunderground.com,democrats.org,denverpost.com,desmoinesregister.com,dispatch.com,elcomercio.pe,english.aljazeera.net,examiner.com,farsnews.com,firstcoastnews.com,firstpost.com,firsttoknow.com,foreignpolicy.com,foxnews.com,freebeacon.com,freep.com,fresnobee.com,gazette.com,global.nytimes.com,heraldtribune.com,hindustantimes.com,hngn.com,humanevents.com,huzlers.com,indiatimes.com,indystar.com,irishtimes.com,jacksonville.com,jpost.com,jsonline.com,kansascity.com,kctv5.com,kentucky.com,kickerdaily.com,king5.com,kmov.com,knoxnews.com,kpho.com,kvue.com,kwqc.com,kxan.com,lainformacion.com,latimes.com,ldnews.com,lex18.com,linternaute.com,livemint.com,lostateminor.com,m24.ru,macleans.ca,manchestereveningnews.co.uk,marinecorpstimes.com,masslive.com,mavikocaeli.com.tr,mcall.com,medium.com,mentalfloss.com,mercurynews.com,metro.us,miamiherald.com,militarytimes.com,mk.ru,mlive.com,mondotimes.com,montrealgazette.com,msnbc.com,msnewsnow.com,mynews13.com,mysanantonio.com,mysuncoast.com,nbclosangeles.com,nbcnewyork.com,nbcphiladelphia.com,ndtv.com,newindianexpress.com,news.cincinnati.com,news.google.com,news.msn.com,news.yahoo.com,news10.net,news8000.com,newsday.com,newsdaymarketing.net,newsen.com,newsmax.com,newsobserver.com,newsok.com,newsru.ua,newstatesman.com,newszoom.com,nj.com,nola.com,northjersey.com,nouvelobs.com,npr.org,nwfdailynews.com,nwitimes.com,nydailynews.com,nytimes.com,observer.com,ocregister.com,okcfox.com,omaha.com,onenewspage.com,ontheissues.org,oregonlive.com,orlandosentinel.com,palmbeachpost.com,pe.com,pennlive.com,philly.com,pilotonline.com,polar.com,post-gazette.com,postandcourier.com,presstelegram.com,presstv.ir,propublica.org,providencejournal.com,realclearpolitics.com,recorderonline.com,reporterdock.com,reporterherald.com,respublica.al,reuters.com,rg.ru,roanoke.com,sacbee.com,scmp.com,scnow.com,sdpnoticias.com,seattletimes.com,semana.com,sfgate.com,sharepowered.com,sinembargo.mx,slate.com,sltrib.com,sotomayortv.com,sourcewatch.org,spectator.co.uk,squaremirror.com,star-telegram.com,staradvertiser.com,startribune.com,statesman.com,stltoday.com,streetwise.co,stuff.co.nz,success.com,suffolknewsherald.com,sun-sentinel.com,sunnewsnetwork.ca,suntimes.com,supernewschannel.com,surenews.com,svoboda.org,syracuse.com,tampabay.com,tbd.com,telegram.com,telegraph.co.uk,tennessean.com,the-open-mind.com,theadvocate.com,theage.com.au,theatlantic.com,thebarefootwriter.com,theblaze.com,thecalifornian.com,thedailysheeple.com,thefix.com,theintelligencer.net,thelocal.com,thenational.ae,thenewstribune.com,theparisreview.org,thereporter.com,therepublic.com,thestar.com,thetelegram.com,thetimes.co.uk,theuspatriot.com,time.com,timescall.com,timesdispatch.com,timesleaderonline.com,timesofisrael.com,toledoblade.com,toprightnews.com,townhall.com,tpnn.com,trendolizer.com,triblive.com,tribune.com.pk,tricities.com,troymessenger.com,trueactivist.com,truthandaction.org,tsn.ua,tulsaworld.com,twincities.com,upi.com,usatoday.com,utsandiego.com,vagazette.com,viralwomen.com,vitalworldnews.com,voasomali.com,vox.com,washingtonexaminer.com,washingtonpost.com,watchdog.org,wave3.com,wavy.com,wbay.com,wbtw.com,wcpo.com,wctrib.com,wdtn.com,weeklystandard.com,westernjournalism.com,wfsb.com,wgrz.com,whas11.com,winonadailynews.com,wishtv.com,wistv.com,wkbn.com,wkow.com,wlfi.com,wmtw.com,wmur.com,wopular.com,world-top-news.com,worldnews.com,wplol.us,wpsdlocal6.com,wptz.com,wric.com,wsmv.com,wthitv.com,wthr.com,wtnh.com,wtol.com,wtsp.com,wvec.com,wwlp.com,wwltv.com,wyff4.com,yonhapnews.co.kr,yourbreakingnews.com',
+    'News' ],
+  [ '2k.com,360game.vn,4399.com,a10.com,activision.com,addictinggames.com,alawar.com,alienwarearena.com,anagrammer.com,andkon.com,aq.com,arcadeprehacks.com,arcadeyum.com,arcgames.com,archeagegame.com,armorgames.com,askmrrobot.com,battle.net,battlefieldheroes.com,bigfishgames.com,bigpoint.com,bioware.com,bluesnews.com,boardgamegeek.com,bollyheaven.com,bubblebox.com,bukkit.org,bungie.net,buycraft.net,callofduty.com,candystand.com,cda.pl,challonge.com,championselect.net,cheapassgamer.com,cheatcc.com,cheatengine.org,cheathappens.com,chess.com,civfanatics.com,clashofclans-tools.com,clashofclansbuilder.com,comdotgame.com,commonsensemedia.org,coolrom.com,crazygames.com,csgolounge.com,curse.com,d20pfsrd.com,destructoid.com,diablofans.com,diablowiki.net,didigames.com,dota2.com,dota2lounge.com,dressupgames.com,dulfy.net,ebog.com,elderscrollsonline.com,elitedangerous.com,elitepvpers.com,emuparadise.me,enjoydressup.com,escapegames24.com,escapistmagazine.com,eventhubs.com,eveonline.com,farming-simulator.com,feed-the-beast.com,flashgames247.com,flightrising.com,flipline.com,flonga.com,freegames.ws,freeonlinegames.com,fresh-hotel.org,friv.com,friv.today,fullypcgames.net,funny-games.biz,funtrivia.com,futhead.com,g2a.com,gamasutra.com,game-debate.com,game-oldies.com,game321.com,gamebaby.com,gamebaby.net,gamebanana.com,gamefaqs.com,gamefly.com,gamefront.com,gamegape.com,gamehouse.com,gameinformer.com,gamejolt.com,gamemazing.com,gamemeteor.com,gamerankings.com,gamersgate.com,games-msn.com,games-workshop.com,games.com,games2girls.com,gamesbox.com,gamesfreak.net,gametop.com,gametracker.com,gametrailers.com,gamezhero.com,gbatemp.net,geforce.com,gematsu.com,giantbomb.com,girl.me,girlsgames123.com,girlsplay.com,gog.com,gogames.me,gonintendo.com,goodgamestudios.com,gosugamers.net,greenmangaming.com,gtaforums.com,gtainside.com,guildwars2.com,hackedarcadegames.com,hearthpwn.com,hirezstudios.com,hitbox.tv,hltv.org,howrse.com,icy-veins.com,indiedb.com,jayisgames.com,jigzone.com,joystiq.com,juegosdechicas.com,kabam.com,kbhgames.com,kerbalspaceprogram.com,king.com,kixeye.com,kizi.com,kogama.com,kongregate.com,kotaku.com,lolcounter.com,lolking.net,lolnexus.com,lolpro.com,lolskill.net,lootcrate.com,lumosity.com,mafa.com,mangafox.me,mangapark.com,mariowiki.com,maxgames.com,megagames.com,metacritic.com,mindjolt.com,minecraft.net,minecraftforum.net,minecraftservers.org,minecraftskins.com,mineplex.com,miniclip.com,mmo-champion.com,mmobomb.com,mmohuts.com,mmorpg.com,mmosite.com,mobafire.com,moddb.com,modxvm.com,mojang.com,moshimonsters.com,mousebreaker.com,moviestarplanet.com,mtgsalvation.com,muchgames.com,myonlinearcade.com,myplaycity.com,myrealgames.com,mythicspoiler.com,n4g.com,newgrounds.com,nexon.net,nexusmods.com,ninjakiwi.com,nintendo.com,nintendoeverything.com,nintendolife.com,nitrome.com,nosteam.ro,notdoppler.com,noxxic.com,operationsports.com,origin.com,ownedcore.com,pacogames.com,pathofexile.com,pcgamer.com,pch.com,pcsx2.net,penny-arcade.com,planetminecraft.com,plarium.com,playdota.com,playpink.com,playsides.com,playstationlifestyle.net,playstationtrophies.org,pog.com,pokemon.com,polygon.com,popcap.com,primarygames.com,probuilds.net,ps3hax.net,psnprofiles.com,psu.com,qq.com,r2games.com,resourcepack.net,retrogamer.com,rewardtv.com,riotgames.com,robertsspaceindustries.com,roblox.com,robocraftgame.com,rockpapershotgun.com,rockstargames.com,roosterteeth.com,runescape.com,schoolofdragons.com,screwattack.com,scufgaming.com,segmentnext.com,shacknews.com,shockwave.com,shoryuken.com,siliconera.com,silvergames.com,skydaz.com,smashbros.com,solomid.net,starcitygames.com,starsue.net,steamcommunity.com,steamgifts.com,strategywiki.org,supercheats.com,surrenderat20.net,swtor.com,tankionline.com,tcgplayer.com,teamfortress.com,teamliquid.net,tetrisfriends.com,thesims3.com,thesimsresource.com,thetechgame.com,topg.org,totaljerkface.com,toucharcade.com,transformice.com,trueachievements.com,twcenter.net,twitch.tv,twoplayergames.org,unity3d.com,vg247.com,vgchartz.com,videogamesblogger.com,warframe.com,warlight.net,warthunder.com,watchcartoononline.com,websudoku.com,wildstar-online.com,wildtangent.com,wineverygame.com,wizards.com,worldofsolitaire.com,worldoftanks.com,wowhead.com,wowprogress.com,wowwiki.com,xbox.com,xbox360iso.com,xboxachievements.com,xfire.com,xtremetop100.com,y8.com,yoyogames.com,zybez.net,zynga.com',
+    'Video Game' ],
+]);
+
 // Only allow link urls that are http(s)
 const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
 
 // Only allow link image urls that are https or data
 const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
 
 // The frecency of a directory link
 const DIRECTORY_FRECENCY = 1000;
@@ -117,21 +145,16 @@ let DirectoryLinksProvider = {
     matchOSLocale: PREF_MATCH_OS_LOCALE,
     prefSelectedLocale: PREF_SELECTED_LOCALE,
   }),
 
   get _linksURL() {
     if (!this.__linksURL) {
       try {
         this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]);
-
-        // Temporarily override the default for en-US until new endpoint is live
-        if (this.locale == "en-US" && !Services.prefs.prefHasUserValue(this._observedPrefs["linksURL"])) {
-          this.__linksURL = "data:text/plain;base64,ewogICAgImRpcmVjdG9yeSI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDQ5OCwKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy9kMTFiYTBiMzA5NWJiMTlkODA5MmNkMjliZTljYmI5ZTE5NzY3MWVhLjI4MDg4LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzEzMzJhNjhiYWRmMTFlM2Y3ZjY5YmY3MzY0ZTc5YzBhN2UyNzUzYmMuNTMxNi5wbmciLAogICAgICAgICAgICAidGl0bGUiOiAiTW96aWxsYSBDb21tdW5pdHkiLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHA6Ly9jb250cmlidXRlLm1vemlsbGEub3JnLyIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImJnQ29sb3IiOiAiIiwKICAgICAgICAgICAgImRpcmVjdG9yeUlkIjogNTAwLAogICAgICAgICAgICAiZW5oYW5jZWRJbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzL2NjNjM3NzRiN2E5YWFlMDJmZTM2YmM1Y2FmOTBjMWUyNWU2NmEyYmMuMTM3OTEucG5nIiwKICAgICAgICAgICAgImltYWdlVVJJIjogImh0dHBzOi8vZHRleDRrdmJwcG92dC5jbG91ZGZyb250Lm5ldC9pbWFnZXMvZTgyMmNkNDYyOGM1MTYyMzEzZjQ5ZjVkNDU1NmY4YWFmZGYzODc1MC4xMTUxMy5wbmciLAogICAgICAgICAgICAidGl0bGUiOiAiTW96aWxsYSBNYW5pZmVzdG8iLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2Fib3V0L21hbmlmZXN0by8iCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDUwMiwKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy80MGU1NjMwNDA1ZDUwMzFjYTczMzkzYmQ3YmMwMDY0MTU2ZjJjYzgyLjEwOTg0LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzQ5MGQ0MmQxZjlhNzZjMDc3Mzk2MjZkMWI4YTU2OTE2OWFlYzhmYmUuMTEwMzkucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIkN1c3RvbWl6ZSBGaXJlZm94IiwKICAgICAgICAgICAgInR5cGUiOiAiYWZmaWxpYXRlIiwKICAgICAgICAgICAgInVybCI6ICJodHRwOi8vZmFzdGVzdGZpcmVmb3guY29tL2ZpcmVmb3gvZGVza3RvcC9jdXN0b21pemUvIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYmdDb2xvciI6ICIiLAogICAgICAgICAgICAiZGlyZWN0b3J5SWQiOiA1MDQsCiAgICAgICAgICAgICJlbmhhbmNlZEltYWdlVVJJIjogImh0dHBzOi8vZHRleDRrdmJwcG92dC5jbG91ZGZyb250Lm5ldC9pbWFnZXMvODc3ZjFjNTYxZTczNWY3YjlmNDE5ZmY5YWM3OWViOGM3NDgxMTE5ZC4xNjc0NC5wbmciLAogICAgICAgICAgICAiaW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy8yNWM5ZmJiMDczMDhiODRkMTYwZmMxYjc5NTkzNjRhMmMxOGY5M2I5LjY0MDQucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIkZpcmVmb3ggTWFya2V0cGxhY2UiLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHBzOi8vbWFya2V0cGxhY2UuZmlyZWZveC5jb20vIgogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYmdDb2xvciI6ICIjM2ZiNThlIiwKICAgICAgICAgICAgImRpcmVjdG9yeUlkIjogNTA1LAogICAgICAgICAgICAiZW5oYW5jZWRJbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzcyMDEyMWU3NDYyZDhjNzg2M2I0ZGQ4ZmE3YjVjMTA4OWI1ZjVmYjIuMzM4NjIucG5nIiwKICAgICAgICAgICAgImltYWdlVVJJIjogImh0dHBzOi8vZHRleDRrdmJwcG92dC5jbG91ZGZyb250Lm5ldC9pbWFnZXMvMGU2MDMxNjc1YTljNDkxZGQwYzY1ZTljNjdjZmJmNTRhNTg4MGYxNy4yMjk1LnN2ZyIsCiAgICAgICAgICAgICJ0aXRsZSI6ICJNb3ppbGxhIFdlYm1ha2VyIiwKICAgICAgICAgICAgInR5cGUiOiAiYWZmaWxpYXRlIiwKICAgICAgICAgICAgInVybCI6ICJodHRwczovL3dlYm1ha2VyLm9yZy8%2FdXRtX3NvdXJjZT1kaXJlY3RvcnktdGlsZXMmdXRtX21lZGl1bT1maXJlZm94LWJyb3dzZXIiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDUwNiwKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy9kOTcxY2JhZmEwMzA5YTIwMWU1MThhY2RhYzRmMWVlNGRhYmM3ZWFhLjE1MTA5LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzL2I0YWRjNThkZDNjMDJkYTM1NTEwNDk3N2I5MTAyNTUwNjBjZmQ2ZDguMTAzNTAucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIkZpcmVmb3ggU3luYyIsCiAgICAgICAgICAgICJ0eXBlIjogImFmZmlsaWF0ZSIsCiAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovL21vemlsbGEtZXVyb3BlLm9yZy9maXJlZm94L3N5bmMiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiIsCiAgICAgICAgICAgICJkaXJlY3RvcnlJZCI6IDUwNywKICAgICAgICAgICAgImVuaGFuY2VkSW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy8yMmZiODU2Y2Q1ODM2NTg1NWViNzI1YjE1NjVmMDhhNzI0NjRlMDM5LjE4NzE3LnBuZyIsCiAgICAgICAgICAgICJpbWFnZVVSSSI6ICJodHRwczovL2R0ZXg0a3ZicHBvdnQuY2xvdWRmcm9udC5uZXQvaW1hZ2VzLzA2OGUwY2NiZDg3MDFhMjhlMmYwNzhjNjQwZWUwNzJiOWExNmUyZTEuMTI0OTAucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIlByaXZhY3kgUHJpbmNpcGxlcyIsCiAgICAgICAgICAgICJ0eXBlIjogImFmZmlsaWF0ZSIsCiAgICAgICAgICAgICJ1cmwiOiAiaHR0cDovL2V1cm9wZS5tb3ppbGxhLm9yZy9wcml2YWN5L3lvdSIKICAgICAgICB9CiAgICBdLAogICAgInN1Z2dlc3RlZCI6IFsKICAgICAgICB7CiAgICAgICAgICAgICJiZ0NvbG9yIjogIiNjYWUxZjQiLAogICAgICAgICAgICAiZGlyZWN0b3J5SWQiOiA3MDIsCiAgICAgICAgICAgICJmcmVjZW50X3NpdGVzIjogWwogICAgICAgICAgICAgICAgImFkZG9ucy5tb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAiYWlyLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJibG9nLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJidWd6aWxsYS5tb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAiZGV2ZWxvcGVyLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJldGhlcnBhZC5tb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAiaGFja3MubW96aWxsYS5vcmciLAogICAgICAgICAgICAgICAgImhnLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJtb3ppbGxhLm9yZyIsCiAgICAgICAgICAgICAgICAicGxhbmV0Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJxdWFsaXR5Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJzdXBwb3J0Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJzdXBwb3J0Lm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJ0cmVlaGVyZGVyLm1vemlsbGEub3JnIiwKICAgICAgICAgICAgICAgICJ3aWtpLm1vemlsbGEub3JnIgogICAgICAgICAgICBdLAogICAgICAgICAgICAiaW1hZ2VVUkkiOiAiaHR0cHM6Ly9kdGV4NGt2YnBwb3Z0LmNsb3VkZnJvbnQubmV0L2ltYWdlcy85ZWUyYjI2NTY3OGYyNzc1ZGUyZTRiZjY4MGRmNjAwYjUwMmU2MDM4LjM4NzUucG5nIiwKICAgICAgICAgICAgInRpdGxlIjogIlRoYW5rcyBmb3IgdGVzdGluZyEiLAogICAgICAgICAgICAidHlwZSI6ICJhZmZpbGlhdGUiLAogICAgICAgICAgICAidXJsIjogImh0dHBzOi8vd3d3Lm1vemlsbGEuY29tL2ZpcmVmb3gvdGlsZXMiCiAgICAgICAgfQogICAgXQp9Cg%3D%3D";
-        }
       }
       catch (e) {
         Cu.reportError("Error fetching directory links url from prefs: " + e);
       }
     }
     return this.__linksURL;
   },
 
@@ -427,16 +450,24 @@ let DirectoryLinksProvider = {
    */
   getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
     // Use the provided link if it's already enhanced
     return link.enhancedImageURI && link ? link :
            this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
   },
 
   /**
+   * Get the display name of an allowed frecent sites. Returns undefined for a
+   * unallowed frecent sites.
+   */
+  getFrecentSitesName(sites) {
+    return ALLOWED_FRECENT_SITES.get(sites.join(","));
+  },
+
+  /**
    * Check if a url's scheme is in a Set of allowed schemes
    */
   isURLAllowed: function DirectoryLinksProvider_isURLAllowed(url, allowed) {
     // Assume no url is an allowed url
     if (!url) {
       return true;
     }
 
@@ -463,16 +494,23 @@ let DirectoryLinksProvider = {
       let validityFilter = function(link) {
         // Make sure the link url is allowed and images too if they exist
         return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES) &&
                this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES) &&
                this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES);
       }.bind(this);
 
       rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
+        // Only allow suggested links with approved frecent sites
+        let name = this.getFrecentSitesName(link.frecent_sites);
+        if (name == undefined) {
+          return;
+        }
+
+        link.targetedName = name;
         link.lastVisitDate = rawLinks.suggested.length - position;
 
         // We cache suggested tiles here but do not push any of them in the links list yet.
         // The decision for which suggested tile to include will be made separately.
         this._cacheSuggestedLinks(link);
         this._frequencyCaps.set(link.url, DEFAULT_FREQUENCY_CAP);
       });
 
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -17,16 +17,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils","resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReadingList", "resource:///modules/readinglist/ReadingList.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm");
 
 const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
 
 let ReaderParent = {
+  _readerModeInfoPanelOpen: false,
 
   MESSAGES: [
     "Reader:AddToList",
     "Reader:AddToPocket",
     "Reader:ArticleGet",
     "Reader:FaviconRequest",
     "Reader:ListStatusRequest",
     "Reader:PocketEnabledGet",
@@ -182,16 +183,30 @@ let ReaderParent = {
       button.removeAttribute("readeractive");
       button.hidden = !browser.isArticle;
       let enterText = gStringBundle.GetStringFromName("readerView.enter");
       button.setAttribute("tooltiptext", enterText);
       command.setAttribute("label", enterText);
       command.setAttribute("hidden", !browser.isArticle);
       command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.enter.accesskey"));
     }
+
+    let currentUriHost = browser.currentURI && browser.currentURI.asciiHost;
+    if (browser.isArticle &&
+        !Services.prefs.getBoolPref("browser.reader.detectedFirstArticle") &&
+        currentUriHost && !currentUriHost.endsWith("mozilla.org")) {
+      this.showReaderModeInfoPanel(browser);
+      Services.prefs.setBoolPref("browser.reader.detectedFirstArticle", true);
+      this._readerModeInfoPanelOpen = true;
+    } else if (this._readerModeInfoPanelOpen) {
+      if (UITour.isInfoOnTarget(win, "readerMode-urlBar")) {
+        UITour.hideInfo(win);
+      }
+      this._readerModeInfoPanelOpen = false;
+    }
   },
 
   forceShowReaderIcon: function(browser) {
     browser.isArticle = true;
     this.updateReaderButton(browser);
   },
 
   buttonClick(event) {
@@ -223,20 +238,26 @@ let ReaderParent = {
    *
    * @param browser The <browser> that the tour should be started for.
    */
   showReaderModeInfoPanel(browser) {
     let win = browser.ownerDocument.defaultView;
     let targetPromise = UITour.getTarget(win, "readerMode-urlBar");
     targetPromise.then(target => {
       let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+      let icon = "chrome://browser/skin/";
+      if (win.devicePixelRatio > 1) {
+        icon += "reader-tour@2x.png";
+      } else {
+        icon += "reader-tour.png";
+      }
       UITour.showInfo(win, browser.messageManager, target,
-                      browserBundle.GetStringFromName("readerView.promo.firstDetectedArticle.title"),
-                      browserBundle.GetStringFromName("readerView.promo.firstDetectedArticle.body"),
-                      "chrome://browser/skin/reader-tour.png");
+                      browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.title"),
+                      browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.body"),
+                      icon);
     });
   },
 
   /**
    * Gets an article for a given URL. This method will download and parse a document.
    *
    * @param url The article URL.
    * @param browser The browser where the article is currently loaded.
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -264,16 +264,19 @@ add_task(function test_updateSuggestedTi
 
   // Initial setup
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   let testObserver = new TestFirstRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
+
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
@@ -378,16 +381,17 @@ add_task(function test_updateSuggestedTi
   topSites = [];
   testObserver = new TestRemovingSuggestedTile();
   DirectoryLinksProvider.addObserver(testObserver);
   DirectoryLinksProvider.onManyLinksChanged();
   yield testObserver.promise;
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
 add_task(function test_suggestedLinksMap() {
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
@@ -416,16 +420,19 @@ add_task(function test_suggestedLinksMap
       isIdentical(suggestedLinksItr.next().value, link);
     }
   })
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_topSitesWithSuggestedLinks() {
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
+
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = function(site) {
     return topSites.indexOf(site) >= 0;
   }
 
   // Mock out getProviderLinks() so we don't have to populate cache in NewTabUtils
   let origGetProviderLinks = NewTabUtils.getProviderLinks;
@@ -463,28 +470,29 @@ add_task(function test_topSitesWithSugge
 
   // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks.
   topSites.push(popped);
   expectedTopSitesWithSuggestedLinks.push(popped);
   DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
   isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
 
   // Cleanup.
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
 });
 
 add_task(function test_suggestedAttributes() {
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
-  let frecent_sites = ["top.site.com"];
+  let frecent_sites = "addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org".split(",");
   let imageURI = "https://image/";
   let title = "the title";
   let type = "affiliate";
   let url = "http://test.url/";
   let data = {
     suggested: [{
       frecent_sites,
       imageURI,
@@ -509,30 +517,33 @@ add_task(function test_suggestedAttribut
         resolve();
       }
     });
   });
 
   // Make sure we get the expected attributes on the suggested tile
   let link = gLinks.getLinks()[0];
   do_check_eq(link.imageURI, imageURI);
+  do_check_eq(link.targetedName, "Mozilla");
   do_check_eq(link.targetedSite, frecent_sites[0]);
   do_check_eq(link.title, title);
   do_check_eq(link.type, type);
   do_check_eq(link.url, url);
 
   // Cleanup.
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
 });
 
 add_task(function test_frequencyCappedSites_views() {
   Services.prefs.setCharPref(kPingUrlPref, "");
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
   let targets = ["top.site.com"];
@@ -589,25 +600,28 @@ add_task(function test_frequencyCappedSi
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("organic", 1);
 
   // Cleanup.
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
 add_task(function test_frequencyCappedSites_click() {
   Services.prefs.setCharPref(kPingUrlPref, "");
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
 
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let testUrl = "http://frequency.capped/link";
   let targets = ["top.site.com"];
@@ -658,16 +672,17 @@ add_task(function test_frequencyCappedSi
   // Make sure the link disappears after the first click
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("view");
   checkFirstTypeAndLength("affiliate", 2);
   synthesizeAction("click");
   checkFirstTypeAndLength("organic", 1);
 
   // Cleanup.
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
   DirectoryLinksProvider.removeObserver(gLinks);
   Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 });
 
 add_task(function test_reportSitesAction() {
@@ -1134,16 +1149,18 @@ add_task(function test_DirectoryLinksPro
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   links = yield fetchData();
   do_check_eq(links.length, 0); // There are no directory links.
   checkEnhanced("http://example.net", undefined);
   checkEnhanced("http://example.com", "data:,fresh");
 });
 
 add_task(function test_DirectoryLinksProvider_enhancedURIs() {
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "";
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
   NewTabUtils.isTopPlacesSite = () => true;
   let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
   DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
 
   let data = {
     "suggested": [
       {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", frecent_sites: ["test.com"]}
@@ -1177,16 +1194,17 @@ add_task(function test_DirectoryLinksPro
 
   // Check that the suggested tile with the same URL replaces the directory tile.
   links = gLinks.getLinks();
   do_check_eq(links.length, 1);
   do_check_eq(links[0].title, "SuggestedTitle");
   do_check_eq(links[0].enhancedImageURI, "data:,net1");
 
   // Cleanup.
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
   gLinks.removeProvider(DirectoryLinksProvider);
 });
 
 add_task(function test_DirectoryLinksProvider_setDefaultEnhanced() {
   function checkDefault(expected) {
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1689,17 +1689,17 @@ richlistitem[type~="action"][actiontype=
   display: none;
 }
 
 /* All tabs menupopup */
 .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 
-.alltabs-item[visuallyselected="true"] {
+.alltabs-item[selected="true"] {
   font-weight: bold;
 }
 
 .alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
 
 .alltabs-item[tabIsVisible] {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -89,16 +89,17 @@ browser.jar:
   skin/classic/browser/Toolbar-small.png
   skin/classic/browser/undoCloseTab.png                        (../shared/undoCloseTab.png)
   skin/classic/browser/update-badge.svg                        (../shared/update-badge.svg)
   skin/classic/browser/urlbar-arrow.png
   skin/classic/browser/session-restore.svg                  (../shared/incontent-icons/session-restore.svg)
   skin/classic/browser/tab-crashed.svg                      (../shared/incontent-icons/tab-crashed.svg)
   skin/classic/browser/welcome-back.svg                     (../shared/incontent-icons/welcome-back.svg)
   skin/classic/browser/reader-tour.png                      (../shared/reader/reader-tour.png)
+  skin/classic/browser/reader-tour@2x.png                   (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                       (../shared/reader/readerMode.svg)
   skin/classic/browser/readinglist/icons.svg          (../shared/readinglist/icons.svg)
   skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
 * skin/classic/browser/readinglist/sidebar.css        (readinglist/sidebar.css)
   skin/classic/browser/webRTC-shareDevice-16.png
   skin/classic/browser/webRTC-shareDevice-64.png
   skin/classic/browser/webRTC-sharingDevice-16.png    (../shared/webrtc/webRTC-sharingDevice-16.png)
   skin/classic/browser/webRTC-shareMicrophone-16.png
--- a/browser/themes/linux/preferences/in-content/preferences.css
+++ b/browser/themes/linux/preferences/in-content/preferences.css
@@ -1,18 +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/. */
 
 %include ../../../shared/incontentprefs/preferences.inc.css
 
-spinbuttons {
-  -moz-appearance: none;
-}
-
 .treecol-sortdirection {
   /* override the Linux only toolkit rule */
   -moz-appearance: none;
 }
 
 .actionsMenu {
   font-family: "Clear Sans", sans-serif;
   font-size: 1.25rem;
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -141,16 +141,17 @@ browser.jar:
   skin/classic/browser/urlbar-arrow.png
   skin/classic/browser/urlbar-arrow@2x.png
   skin/classic/browser/urlbar-popup-blocked.png
   skin/classic/browser/urlbar-popup-blocked@2x.png
   skin/classic/browser/session-restore.svg            (../shared/incontent-icons/session-restore.svg)
   skin/classic/browser/tab-crashed.svg                (../shared/incontent-icons/tab-crashed.svg)
   skin/classic/browser/welcome-back.svg               (../shared/incontent-icons/welcome-back.svg)
   skin/classic/browser/reader-tour.png                (../shared/reader/reader-tour.png)
+  skin/classic/browser/reader-tour@2x.png             (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                 (../shared/reader/readerMode.svg)
   skin/classic/browser/readinglist/icons.svg          (../shared/readinglist/icons.svg)
   skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
 * skin/classic/browser/readinglist/sidebar.css        (readinglist/sidebar.css)
   skin/classic/browser/webRTC-shareDevice-16.png
   skin/classic/browser/webRTC-shareDevice-16@2x.png
   skin/classic/browser/webRTC-shareDevice-64.png
   skin/classic/browser/webRTC-shareDevice-64@2x.png
--- a/browser/themes/osx/preferences/in-content/preferences.css
+++ b/browser/themes/osx/preferences/in-content/preferences.css
@@ -4,39 +4,16 @@
 
 %include ../../../shared/incontentprefs/preferences.inc.css
 
 prefpane .groupbox-title {
   background: none;
   margin-bottom: 0;
 }
 
-spinbuttons {
-  -moz-appearance: none;
-}
-
-.spinbuttons-up {
-  margin-top: 0 !important;
-  border-radius: 4px 4px 0 0;
-}
-
-.spinbuttons-down  {
-  margin-bottom: 0 !important;
-  border-radius: 0 0 4px 4px;
-}
-
-.spinbuttons-button > .button-box {
-  -moz-padding-start: 2px !important;
-  -moz-padding-end: 3px !important;
-}
-
-.spinbuttons-button > .button-box > .button-text {
-  display: none;
-}
-
 .actionsMenu > .menulist-label-box > .menulist-icon {
   margin-top: 2px;
   -moz-margin-start: 2px;
   -moz-margin-end: 8px !important;
 }
 
 #downloadFolder > .fileFieldContentBox {
   -moz-padding-start: 3px;
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -873,19 +873,22 @@
   background-color: hsla(206,37%,4%,.4);
   color: var(--theme-selection-color);
 }
 
 .theme-light .devtools-tab:hover:active {
   background-color: rgba(170,170,170,.4);
 }
 
+.devtools-tab:not([selected])[highlighted] {
+  box-shadow: 0 2px 0 var(--theme-highlight-green) inset;
+}
+
 .theme-dark .devtools-tab:not([selected])[highlighted] {
   background-color: hsla(99,100%,14%,.2);
-  box-shadow: 0 2px 0 #7bc107 inset;
 }
 
 .theme-light .devtools-tab:not([selected])[highlighted] {
   background-color: rgba(44, 187, 15, .2);
 }
 
 .devtools-tab > image {
   border: none;
index 8e557e03821219211c19f65a90ca03d2f0b3b03f..be346b3847928f94d7a0bbca019f40d86edd0899
GIT binary patch
literal 2672
zc$@)n3Xk=PP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp)
z=>Px<DoI2^RA}DST6<6wM;aec9%2%ZND^|yXf9g0x~jXY%9Z|eRhPOyEYCYjUh=S1
zw2Ve`M)CA=CKrPdPCb=}$chR+5uz4IjG&-s;6MZx78Y2R_dbAifd%%(zGi0ox@UXV
zWnq_H0|~cPKc?yKnfZSGeck=__YFbJ+|L7I9>jbIF9>ftc;U8rxbK|!d19e_Azp)6
z4DmL^5{P9Gzr<LE>rh(|?(2*1_58U6_rZd^1@V5?<*Tvn;(@|}QFZI6j+AMQ)Hq24
znlc0PxQW&&w1z>MQq$JiCoj&uU9g?-@p&KK7YyN#pXK>e3GR(wy5wr!&27EUhbwg?
zZPb{|B!Ae@{k=$AdO@JdIrvm@Vr9N`*RS)$AMdRkO3G|iUHPt8ThcIW5Gswd(MXv|
z>9De{py>W*@L5YC0%n^CvfWo8esMe{B}}dw?;a=3)HAuR>5FHI{}7z+Xe%<iqKFI#
zwrpF^T)Sg-RSYLo$aHloBW)Vh8D-g*uf@S<zXS0qHZ6~toSbL^E{ceV_&pr8S_5UK
zD`mR+CD%l86NF=Nu6Kh4T^P8`63ci&YGIZrwp6OC(2-^unb@BA_>bVbUdNjDm<i#L
zTHiQ+DkTirvS8FG{zJjQr&ghD1?BWc66Y~ET^87v*}isUe)VnN_WD7CKu^-9%Q<-;
z<GJPgV@%2eKOp#C>7#!bX){$U);^vv+P0(13Ygg$L1gw=!08E}aJAO6#2_+kGzU#9
zr$cj#(J+^n4Bs6BvEXGi;el27R#koDDLrMTQk#?)EW-D`FcA(OU8~UWdL9bP>4XAz
zhgk%-zjj*s*=mI$j!D3GeExLkgvLaZ&E4WWH0Q7txJ3d@qQzNPb9QR<MtWbF<Rp>O
zxRFTZ#}X;L7z>)BtVnL$K&0{_tw`oY5U05l%jk;!KaJ{O|K$1&mTN<_73jICyjX<L
z!k#ab;|A(}S^3vcmN)S{ba}P$zjWzR1W8hqk)%up6RH0>0N-t-D9Zfs;lsb7MFh(>
zt?4WX2?<$Fk<>T@m<ymO+O%ueu9Z->h0~c5GM%8Ip`rY_B0LL?MpKPfCNF2Bzc4g3
z^h3y)nrj50Oy>0T^uOSZ&co5@zX6$+J9>ygZvcr$6G3!zG>D3da)@o)wt>^9PlFM~
z2&k&60tfzn0Bqd2(dj)~wrl|v<rPjz!1oKW|1_l*d{?Yk@d0E?J9<D<V-tWHavA&M
z_k-~8aF_eX$Hz~rg(F9f{0YkHXRigc+XV;(!sE^!P*+#Sya)+ZQc~g&1qB7*=+UE0
z-_4shgY4{VP+VN>uzzxLGSheW?%mUxl3XtT7P}gr_U01|Rn+Q~K&^#9WMm{7jcI|1
z$e+uZGF%QF_Cw@cB|ws-4DZkr%n0pt@4<(Wt`b215A+X!rlzJzK=oMVE=|y)q9T_G
zATwIKcJ1%5i(y@T{0a*TW2d{>p1}3%*Qc~)RPW9T;|>7?p-`A$yBeWCuL%T#BkmFq
zi^QP1x_S~MV#%x$kjZ4%@XqYXPT)lY0|QswC7@EOz@U6^5>WkY5>TmB5AoT09_#xo
zRw|Vx|1Tz_8`tr%gEyZg5Cki^?h<HiX$5!g+?fPaKbr(hMw1wCXjTG?)oS%)cL^|{
zrhuDng1ZFJa*zF@`Rp7BgcBpzT>>|5++fP~$j!}lNf6RRGH{mw9PA%#=Kvz$-y!Tc
z<}QJnXEos5xpR|%>YWK~G#HT?0IgPw>>1p>dl#%<zusj6csCAYS0Tm=Eh{VA{xT*s
z)y_JN{Q|8<%Rr$}pwVOqtXsDZK%d_w0$rV5CvZ^B!(~U#o`6QJaR|LmKj~g1Flrkc
z9Rq#RK4!Ph&dLS}2?=OK8E<vRjvWla>2~77hY$bJ{;-cG!`o<fJG+@%$92i83=0bb
zhrc=uh6abg<FdyfE-ud6hDLhLnl&IbH5FuJWB~YHaOlt>AQFkd{{8zIqE1Or)EJJl
zdfR)Wi~1ye7o8DM3@ezCa@9TFyLay-cN!5`y?QlsUSJ6z96Wds?Xcjv>^Y+;G*8fc
zv6o?Vbo2oZBG0owjrl>>cNJ2RGZMqY!;D*9TU$FNYHDggettgKv}u!7XsB{fUS7_$
zS5#CmUTSG+Dd2E8Rssk}18_rSc5_FXxOwwt3=VO6Om!j)hRoYt@Cco@A@uk6gRNV)
zGD6zkgGRSq*v^=vJplxyHFzon$}uH#0<?3bhj9GV+tHc$RjAg#PNxM|(bC*9NeGre
zJ-2=W0WU8vW;J^9<O!n%y<X2O2CNw&nczzKDUOUg3^$@fLa<h?t(mpx0wmDc(Fu4w
z9<yio`1pWZw{C%efB=w~n8?fnVVe+)j*K!SSOU<Ty758Ad%C#L!ks&Ju9~gL1hQ>W
zuL!iYwSm06JOD=(d6yt5DGB)d`!l1D&faJquo^(i`;{wKLh%{E)0N0vU_(R0zh=sm
z>^w<krIAXcjNb&$At38w7DFO1Fc2I&b__@)5@rcT=7(f}=Z<sOb(rr?U^lp_MUXu>
zJVHMdNXv`(q6Y}YeDVEazGwpOH;aq!^IzaWv$$v~ptgIwp5j}T{E~(aaeIeIDo?tQ
z^You5Gis~3JjLzXx9LY^WpoW+AkBSJS1y&w$*x{W$;$+I+0gnso&EAhgX1QB=snTr
zL~3g+ai%SXNNbBG(psX4l$IzWO|XGTYqlVjA8COgl1OfiAkrXEInAbw0=g%kMTg$t
z9q>Bz{Z?XM?sj5(`kv)`l8+WwH*#|y*FO2pS67o-#B#M{aHJKdpZs1XPvD7fd{@vT
zy)$m2sejjxrBBWeKrrEP9QQwq!{<}x2XGteb4C!w48TclEb&cg9PwGsC&Z4k`@+-j
zp6!tIn>wVjDmW+J!)w0hk4fqwpChF7hsp#4Wu}TG<Bulg!mOzzOoom9+ru_PlYz54
z8SPQT8Ga0LtY-7sr`?4^qe{Ap+jJhg3kzo=>BpMWn`bgJW0VF`IjpC28O_Q|6Np&z
zVpHVCCJ1elUP8%|%<h;Yb&9lpwNb6s8+AAH3wPtw)`FSLSh9(}0K(UIS$R$ENj+&c
z^l8YkEP*O#(dFJv6S9=H+F>)3tiZT^!TFxpv}WbS9u+A!kThA>+;%Z2C}=tM^JgT%
z=V*yVBeQtv(xo5p+PbopdUC`_naxm;!Ga!b(Y{JWBFYV;k!64dUY;l2w)?+>-{f|v
z3p&P(1A3CCRR&Tm=<U0`a^=e3qQEkqbMuH<{;}Ug_~97kyYL|L5x1o+b4aTfp-d*q
z&XY7StBo{?^1vqAY=+CZS!*N>3az2PQP_R$%P+tFGyVi22;2I6;wS!wf+gaO1$Z5=
z*GQ=4&}IJcRLYt4sT@vtL`1|YREOI_usi$;Ufbuy#~-#|s(53;7GN#;;j#~In{!{R
eOh;^11^pj`T?e|8rgpOc0000<MNUMnLSTYrNgGB0
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1a60d93ca9311f8bf9e0ce3f6f175370a69f648e
GIT binary patch
literal 6426
zc$@(m8Rh1QP)<h;3K|Lk000e1NJLTq003YB003YJ1^@s6;+S_h00001b5ch_0Itp)
z=>Py2)Ja4^RCwC$T?trJW%s|NqAV&ZYN#o$m0MX~^_Y+OS%1r2D?ud1<<Z0~%Ow>>
z&0H{4QXs~^n*Y?#&(thS%`JgMLEKRUVHoy(XNF<+`@iRM-r=1)!#+cx+~+yZUFOcc
zGw1idzjNO09ENH9n-$6nl(eWq$URn+G;eiKHYl?tC)S*_=A;cLHk{a^*qIPpyr&I*
z2fo`1pF;!Fya|*}6y6FIXM+lD$B6?c9XaXDNf%CD<iv@SZUS+_&${BhojB=$&#=d5
zw#Ij~c;3MfD!mP*@Q$cxP-;(3`f$>plUFzy#K{m&UgP9-PKKepj@O2AGMJNBIT?W8
z?ZruV1O`B9kMC-OHRQPhgDAcwmPT7tdM7Nspzwa23<QO)TC--#xy#qQqMk~QrsNjg
z%9krLr7CSnnMS8jYIGW{j@9W{R;SYHG;+08S)x?S3gxQYwEU6>(UR0tSFYdq*2mY^
z-PYE27=qUy0ReCwDKJ`7G-dTXDBd3Fp(`pK6f<P;vSpKQ+<CAst)L{TOs!S1X4=$R
zoi@8fo^b!E`2T$O>~+W5@-ipRNL^6t+G4F}^(+BlO7R`B1Ohz_nlNF)=sS;N4-}QD
z^M#62Xmr}lQjPRUfigKbO&;-MjP&@%TlsrF56<2+{(Qz)eU2r~>vBlq@xdP%U(CCf
z?d=tj|6M>-(T|r><e?9972;H>x=^OlYJ|@%RcXp%lhc1&`SsURk<$7hO*uYGKv*Ha
zw#I5s6yF#A^r0iiJabCQ;<P$o5q(M3rAXBUpzQUL`Myk$*o~J!@pN9uu?WwFLOv(o
z3n<@vT+Q)1pCtQBT&&LG=u1msp(~WDv#*AQZ?m(rdmU?H52VDl0%~e<rM?w<9uCOw
zP~wN2JacvhaJgOs+?Fm?7yl-aUhH=)WuAe;X+>A2biMN9T}>X-;BaE|LxTH(nBrsN
zB6XIAvmxYn$W$8H-?#7kIXXJNfi<EV(xe?_tgV_-sc(x_8jRkcU3>S=E|RNr$RFkm
zr|xd9BIeUenXB~*5>d*gl^B4>WDlPOppfGrNx2B`H3Fvs;6cYE3$CQd!)Tf+RcXsE
zTn+I?YU+)4svXi&%f<`>;d)Z)!MODwF=E8X#O(Y~9a0VOdU%%n!CR+O76}!iR}`&G
z10{3vL%d7HvBX8c55UiY@G~JuK@@m`n$h7$$;@*}va2Pk3d7GCZOK<(t(t=FZx5^u
zwvEv?(3E3~{0W}TAaEfS8m*d8&7*v!<jtScJdKo2D~BkPD3n&NUL+OAfs$!P*M#@d
zU<C+(0H6^FyMsyYV1J-?!f)pMTphT;*uHSa2A+Lmq9mexYy4sG1A7CHiqo^NfZpMc
z<j6I0@LmiQuP^B)O0!;sN`~Y3X^aTZ;ky1fzI-pD?llPnsX^r35+iy7T1+TbXX|T2
zu{?3&#EJh#+wdY%QtN*j1j6^$$oIgN{T3}+Hi2^~bIVITtGkgQzt_SP%>)Q&g(y9c
zQm%or<IB&35<b=MK}6KGK*2EqMF=7dE^R{uhWkHHrN3|uKE4o@sddVOKOJ3&l++Cy
zac%x75NKoD4ok($JN^7-aU8EgW2X5&s_1`>N<3{u^(CGv-K1303Kc5d=vrL)v45Q5
zbs7l%nS$Cu*EnKx;F_qSM$G*yXG3n8Mh^c8THY6fL$_i}y*t{v1_yzropxw!Upe;k
z=_SzOgddtE*T|r8WW@79!V*u>h+a{Y^7IN#_@w-pI_H(n?dfBK&$&JMeh>wWh^koz
zph#Q{3_fk74<&pCjX~l$M@Gf$$F7YY{{#pxP_)A<pEI`JTJ))trJB5#jwH@8=6VUG
z{CF25PN(U_q(X_p<50L`Q9{Kr>_rF&Ei?eM7y&@hQ2<3tRBedJQamJfAvz)D40il=
zM>=W{n?SrBd*u9=kDfTWoG2ekiH#^>hJmpSpbcxhp<&h21yxdMWfKJli9a?F{vK{0
zfad{%YX%@tfYG4RG)CJBVx$cWbw!PUZVuv7L*Ek8Q8#1*YfHA8t<cZ6LvN@5F2B9A
zRM@nSE>I>JDc*qR5dc&C9u)VfK?xTn7%QEYfTx1Jsm};l!Wtu}8MKYi*9eKB=R~=x
zAm@@xiHD~P3BSD)yT4yVR%m4j5Qx#WK|jCG(&fu16CXdZSe3_@d?Dx4t{<&<J-^c>
z-b6_!%KSk*)j;4neQxR=a!m*jh)6(L1~r0mZ4JC6y1gQHyPhGx!<M6<J9F;BDja|4
zf(6v<E#?=H<G|qb96EI9J1UJ<s?};)g<7M@Dpkd2$y9L$A~h}9GSyRaNRCV$YwEZ@
zk|kAAzm+<cnHHH!QwBQFa?Zz|J$qcSzpE1#RI}LwE38&+k?RI2mC8qK^JcJdW}&!G
zpFVGK`gw^m$}gCUdTf#R2P7sY?$>HK)SJaf<Z}5PG|Dfcqf&1wqN%6X>eZ{qb1%|r
z)Ec%~jD^#UE;2IG8^=64QSF0zc|G>%?+;QclyS|9t`>w+Cnu*jv5`hxQ>(g`{x&$0
z)t5td10dY2<m#oMpx^?IId?|-uC+m?4R3dAYwMwaY_oD}PDS5*^UW0OmTzB|1{x{-
zz+?RqC5ii+6<a%zm&)&A1FZ{H3u?{%;C8;oAu6lu50OYD?3OKC*tv7(va@E*s*`wl
zc(98XFJ`?rc(ccj9b>aHvkcympO?@6a{3o`)5cBg(xprJ`|J9y*|TS}8#ZiUA3u6r
z?^^KXmtT$()B>y8cs@M$$Hm6^)%8c@<>s+7XU=4&PMvC2nKo@28yp<WzvJG$d#t;=
zyG7oSl$ca65TsJ+4IDq{jE%Dz(NGI)PJ=HySfkMt)b$7a`s=S|R}7I)KKX=KzMGqy
z#olr7z`^>~0`2hO!{0@ZsBKN!1;qG5mFachz=4_d@qG4+_FGIjq?B6n`CGSct+y>m
zO-Vh3Lzx|G(mE!_7p9ZE%vGV#`n+5u5*hrVk3asHUA1aet!3rPmF&WW3t1Nzmudl7
zuwVgye_h_=>gsBC0Mu%AHjW#-SQBGR=m(51xSmRL0x)C73|0q&2elNwsjt=Y$8m9S
z?B2b54W7Gw`*t=eDvH0aF3%^jY}qnH0KVO7ZkMzjGK^Jq^yADKxs~;OxhVk5<Y%*2
zuU_S!dE&$g{+zQ_^?t4;0N|aGyu9CPLPNy+0pEW>adGk0#tHxd;nb;9P{qymOiKa4
z{l;|cr0YU;Rhy(ATO4W}#FeO=#tZ;28uK=uECm2~L#<l1dL74)NiV6<MjBt;d-v}B
zwmJHJsscc%Q1Xqc+6a8dlC4)u0RR#b67m(!u4`|~4jAzNty{Oe%<<By04SA}*4i!P
zn)gi>0PLWn?SK&g);IyRUuI_J$z}k|3jkMppHi*jHbO&e;Qw>Vxoc?v%H(CNpPyf?
zL{X+_vH)=Qsu0@)UD4JW0bqkxdJt!|Gc64O@nmWvmhQo{Bmms+*WhZ-ZrIjpAq2n{
zTWdqOn-vWKKw}e&)&Nds&z}7O_6r$#LZk%@LyxbUYuvXg08~m9`}pzWT8UbvZqfi8
zKYn~JwuswS1_1b}!^}1G*ldJNx}v?PE(rqw_%?vEv-3OVdSNpFhH~%Ty)A;afDr&a
zU=*hz003pe$W?6wJg24%Kzv-hA1?eeYK)OqaF6ZVw@+;d06=QnTiMi&up~heSQ!BN
z8Zc(em{AP{!1Lw>J&t<pQyl<cgx_cgBm6u95Ed4;t|}Wq24Wh-4Fw|%<GHnwrfvhy
zo;~YH4a~GO)fMf8BVp=i%?pAy1QA%^BD@A-X#jTZ+Vu&}o;GR+kgkZXI1{4qSptA4
z7#e@jrOgj-av;Dt^e@3RIEFbzQ>IL@R9A$K8WS+t710KJ>RyGOJ4*uq3Q0&vsFl>E
zB}|kLN(N;EhyVc2>1WHz%J{+n^tNQl5*`2of|+Z899P_%9GthJCS&z4Dk{44tQcWZ
z@<G8esf;ZyD&|MhsD%|Ufr`JrXwf2e^5n^S5X=lfs$f|11ykbzunwTNI8MCpSpon`
zK9usJ!Xh>&JBPmp+r^6)^&r4!pMU;2J8|MfJqX`!`POW|qf}ZNj_b%eRW>l+5{<)4
z8#Zj1Z0RA%h7wZxp@f6-Q<76yrBcZ&e%P>K{5((s!l6Tl*zx1X>p|G^%@(tD0E*+q
z{<wzBp|W|RbdTsz?n`C$eQ8iokO2VTN@ize*GUTU3#uIx24xo(6!MBD-^)nP;FZoP
zfPM7n5qm4*7F$|c$}4@qfB`J`U)eQl*62alvuBSUgw2~a^Y5%L2vC?kJUsrVG7rd<
zA%3N-wCs_&Ye0PkX0CaAd$ZAxqYd7ZC`n|uZQW*ak}gomq)C%_rGEI~hwS_Bzt5(m
zq!<7I0<-A|2r%($+_-W4Y;2$|0zy52kYWI+Drd*h<HCKC5|e(YF93%Q9<tcH&67W$
z<lk}S$`yX1Ze{lY08pAip<~93VMmP`#U>{w8vy{aeED)c2>$;5JUu~;A|OBws|yI+
z_iy8h8Ans&!KUm$4;L4gQT4S0!PkN<I(hcupBJm0pbK0M0E`_wmi^#^57?0-M^*{|
z2)Lpoj^N*a|IJev0RfD8T|j{4UDyTTP~AEK(jo>|X(0Eqr1~1bb?eqy6ol%81_0=1
z#E21WQc_Z-0D!<mU;+Zz6955h5V*0m1p(&QVw}=$RZYqgwg-TQ`o=`Z_?lrl27Bq!
zCEgwc1_sti0s;b9FE6iZOFPWIUcGuXPaovF`}XZ)ckkZKPvBj@em&pX&B(~85&)3-
z^XFH9kdwovr=`~k1g?#7y{7adT%Oe3!NCEBCpG4n$5~&2@c<}^FlF47^4G3i%eR*G
zv#C*_0Dudcnv%*sxc`8?aQ*^YSXfvs03dVb%+a@+VHp%Gje_<9w5Ds~9RY<OK74o<
z4nwxDYG!m*YXtsoY~98O4jeF0zOSz@Um{K23#Ap52CnG6yZ6|0=g!p_01#MWMOx1A
z99VWGEs@r!7U0@|LFoP1)pVgAJx#Hvr>Eyfjct7}lx(s_VAG~eRVqOUz#o77Q7ZsI
zz`Z3PkZud81=Rt7#<verdaYtd(8l;x&I5>SY_*`IxP<S!$;`~G^1iy%07pkhzHtV|
z7zF4oBqT)Ne?&U)s-z)}rT~5aZgq(pLBf*pYsJjfHDPuTN=mA|hX&yOz5DF>^XIDt
zz{<*sjfsh=GCGP%rK%Eus0UFyFlIzYrVf!)tOT{-74FXlH^l|h1OQ@$|NQe${#*z^
z&z?QmBS((t&%tdaTVi4&o0ggeD_wa0hZ+C?R7OECwG-!oI@Kp?iWL<Z@uh(S2fkyj
zDT@sa0B|-aFzR6xd-?KZwy4Mu08l=7P_RK;fq)Pi8p>aL6!nO`_SZFj4?u!DS{Vfa
zQ^4v{9DMJ<)H7}n2~!3hQ9l^fZ>j)*9}j&<A;BR$brArZot^a!GuXny!t?;V_uhN_
zHRwGJyB@}`o(CYIWn7sn$~`*-r=fQ>NBA-;DoP?BvWBa62~7zAP!bsI%#2L-Y3x(>
z=8c=YCj;BOdGpxR)KvZ$Y5)P|qmMq)g8+R^u)(T%01$u#3WF|g6BHx|rl6u`nI(#%
zLC~g2lO~O7N>l^@0B=K=f+Ru08)DKOO%x6w`0Vh}570zJMCbt+J$kg>=*B#WF#>?5
zp3|pKFQDSa)hk$2Wf2p3D!Opt!m_3kI|u*(zJ``8&_`@cEN?`~2Hgz*J^o)lECH<S
zm&s)O^$8Ou=s~!9_ilMD;HauH0MgRZj+m2rs-hxz!7Juk#?%laV!+p62Vj6EIVo8`
zA_m47xE*K)8fdWW_4e)C{AYLW+~JkqwryKhEEel&2tX79fXk0a6kUr2aHZo#^U)J8
zhhD!J9v;r#xN%draNqIY?cFwRM%iTVwqcX~bnlJ!uDGARej|6y+uqH~yL`WX123*9
z<aoN*dcC;N$7}iXwyx{E;GEy%Ik~3)9PY*M*iKus!S>TN8*Qhq*kn8Di!HX}7H!Ke
zEXjY8l%CjilILEV_hx+GdZdeg>){^<wf@)mgB{+TbhMyI%5xO_w}OHKzDG`yo|R)W
zde%Ohaf^K4Ub_9@<!d3Dn>TN=*KgfU!L%nmk)~Uli+ZYxnC^gvW~ewVD-x=7Nokqx
zo3IoS;}bE{Kz4+G#`xTHXMApz%Z{5fnD1_t3mp4Ixbd>>M)`h6_;ltw6gb`yHl6Va
zcjfnRzKuKfF(KRV9{OI8t)b<z`HDNU{`XnT>N6fU5_2v)t~<4mS$22<vry#8%nw+?
z%=B5#Ok4joGvNzwuR~`}X|)w;O_H<o(}%AK^kru6T=|~w?_0V5tOEOySy+-cc<|tN
zv9G_Qx#NXa1wtG2aeLa?*}axsEQ{kaPUu+Ok1@r^E3z3;G5Jj;WuOCh6QLvOl5oQ$
zvMz}a9Y}>X)?X8RPQ*XvQ)XZ6G{!G_8nfpC2TtVl7Tdzg3&NIAcV<iQOlIA=+04ol
z^BK<{7cp~oEoWRee?5G4pzr*>Ck~GC3fjZCdV4V*zAL8fzqB_wGg||8qOepU#iS0s
z(dgPXRE`-dDpw4k-m7!x&co6Rr1wcE-?NFO7mc!Ika*I<oB_fJ>PEQ&stH>!5`?ff
zh?tH7pnf0i!UM7Akt+|x?z_{OJ@?$0?{0H7G3+zun@h8qwP)rrOAkYh_=1_e>nmo)
zw$;qc?W-7%-76L!z4}v9YPuG>F|rD!`J6ZSK6*Tk4XOOfKmh(5nvjuuh0J%lpR0(2
zxcdgRfds-P5z6Vz79@DKK9s#Mx;hp0+#vWiiHJ@|MUSia9sv`S58@x|!h^9l+Ep*#
zKbp={<<^i{jMuq&%&MOka6l|(JohhU79U)CAoxL8T1JL0KR=&M$;wX|Hf-2%O8It{
z3fNs65IUeu7<@lQ{F4sD+2@pLN<FUSco`&Q69&UJpe7R$*6D-`!i*ZilqSLe;dE4V
zQrkpS!UlN1KX>f^gsTk?-I$$`pK-M0!EC-f2Y_()xx73=oSp@JKv44I)3fh6IXV3c
zIp0x`Rp}qgBhwOXf&;o?uO2>jd~umt%lmqegXL_Z>>{7lj!w!(rwpN^teeV`L{giW
z$|)lRNT~EcVd@fzi&2WJy|J7;abx@*&R~4+yE8i@W^KKmbSR^sM8)_2mq>NN;kW(K
z)$ED2p3u%eo@I|V%p{*o|H+dlkIs`R^qnkOa*cG=jXWQdwjn6q)nv%~%F<JbB$a1O
z64CMhspLSy{9=R>$#3v~I-S`UGh@t|gcZ@br5RkyR|l=%!qPJ7mTf-n*vKMzV{Dj4
zmeh^Ts6^6PIa^y>4+*>bz+a&*cj>@DN98HSlP_ehGzmh0FxLzXhQu(#g;6pdfx-g>
zxx?rZZz8*1zhg=BuBDbnC?KIF$DwMjrT;WJ<J^D&1Kz@Z{jQBAeN{~px^=)3^YYlS
zWB(2LfJt64NRSp?piG>3HOtE=cch5+m?*<d_Cnec9+P{8Nf#<f{`uzVG|!t^iu(#G
z$!C74GIjmNjV>5?^(AyOn@mng+Ag%k+5ocx2KtK*%*`&AiwO`=)+EKM+#|6iCn23+
zr3pM~x)7FPIy0WIbXS%Dl(R=W?-vC^+7*?yBB5uITvb5jk?ez|zCA@bO(wmqur|;c
zRR{0f<@Z@eq4cqiN@dF#Z7rlaJP=)S6!HWcC`^=SxEG4>y2!YsLz==JcMAd|vJ_Dz
z%JS4JWGj@bvrhkUel;$jAemG<GEFR#YIWTgc1TN3*m@iUS@)tPsV5<oEUge&77V$_
zBC{2bkH?jqSrL}Ey~`o7he5vJ%5n&E0-5_D!80U)4o#QePL*m3Nq$IuvS_tdEzZad
z-o0<12bTE0sQ6B3$DWxq=fbSo1c<IkO)zWhwSfHx=S#Bku9PS>g@mFEQ-rWOD7o2k
zO=+4;QzR)?=RPe|rpFd4Qxl3+IgmXYKr2#el^TJv=}g+3EsFf@&r56Dw{QOjdN$5X
zMOt%vrXp?aGnZdp2!t*2t0R`Y-U!AJFxr=buWuG-<_71=%93SDwZarARc(;rDPN|@
zO35pVxPIsU_nw|hCgL2;SCI;PpnY<{LectJReWVYv_Kot21}j;g5iXoM;`<PW|qAM
zK}5Fs`nvt+=hI(6n%t|`!*@nLd=dz01w$k5`hxL1e(IMm_XUbP-hTV-kr*6lFqZPZ
zSlhax(mSAoV1pe0TqwS}T49Y~*r9SdARt{4lpfd$bw+Ud3ZxHy)(h|LhTnC>XSKs;
zKle(nNzI@Eu|dVzqY~R=ZRsEo2mGuZ0%(Uc*9M=}3ZG4s-mLf<0z!k*5*2A(iL^p#
oNh!No;T8a<Drw%Dx0-MN4-+oM8s$K!R{#J207*qoM6N<$g3jVX>Hq)$
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -118,16 +118,17 @@ browser.jar:
         skin/classic/browser/update-badge.svg                        (../shared/update-badge.svg)
         skin/classic/browser/urlbar-arrow.png
         skin/classic/browser/urlbar-popup-blocked.png
         skin/classic/browser/urlbar-history-dropmarker.png
         skin/classic/browser/session-restore.svg                     (../shared/incontent-icons/session-restore.svg)
         skin/classic/browser/tab-crashed.svg                         (../shared/incontent-icons/tab-crashed.svg)
         skin/classic/browser/welcome-back.svg                        (../shared/incontent-icons/welcome-back.svg)
         skin/classic/browser/reader-tour.png                         (../shared/reader/reader-tour.png)
+        skin/classic/browser/reader-tour@2x.png                      (../shared/reader/reader-tour@2x.png)
         skin/classic/browser/readerMode.svg                          (../shared/reader/readerMode.svg)
         skin/classic/browser/readinglist/icons.svg                   (../shared/readinglist/icons.svg)
         skin/classic/browser/readinglist/readinglist-icon.svg        (../shared/readinglist/readinglist-icon.svg)
 *       skin/classic/browser/readinglist/sidebar.css                 (readinglist/sidebar.css)
         skin/classic/browser/notification-pluginNormal.png           (../shared/plugins/notification-pluginNormal.png)
         skin/classic/browser/notification-pluginAlert.png            (../shared/plugins/notification-pluginAlert.png)
         skin/classic/browser/notification-pluginBlocked.png          (../shared/plugins/notification-pluginBlocked.png)
         skin/classic/browser/webRTC-shareDevice-16.png
--- a/build/mobile/robocop/Makefile.in
+++ b/build/mobile/robocop/Makefile.in
@@ -44,55 +44,23 @@ manifest := $(srcdir)/AndroidManifest.xm
 manifest_TARGET := export
 manifest_FLAGS += \
   -DMOZ_ANDROID_SHARED_ID='$(ANDROID_PACKAGE_NAME).sharedID' \
   -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE='$(ANDROID_PACKAGE_NAME)_sync' \
   $(NULL)
 
 ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
 
-# Install robocop configs and helper
-INSTALL_TARGETS += robocop
-robocop_TARGET  := export
-robocop_DEST    := $(CURDIR)
-robocop_FILES   := \
-  $(TESTPATH)/robocop.ini \
-  $(TESTPATH)/robocop_autophone.ini \
-  $(NULL)
-robocop-deps := $(notdir $(robocop_FILES))
-
-ROBOCOP_FILES := \
-  $(wildcard $(TESTPATH)/*.html) \
-  $(wildcard $(TESTPATH)/*.jpg) \
-  $(wildcard $(TESTPATH)/*.sjs) \
-  $(wildcard $(TESTPATH)/test*.js) \
-  $(wildcard $(TESTPATH)/robocop*.js) \
-  $(wildcard $(TESTPATH)/*.xml) \
-  $(wildcard $(TESTPATH)/*.ogg) \
-  $(wildcard $(TESTPATH)/*.mp4) \
-  $(wildcard $(TESTPATH)/*.webm) \
-  $(wildcard $(TESTPATH)/*.swf) \
-  $(wildcard $(TESTPATH)/reader_mode_pages) \
-  $(NULL)
-
-ROBOCOP_DEST := $(DEPTH)/_tests/testing/mochitest/tests/robocop/
-ROBOCOP_TARGET := export
-INSTALL_TARGETS += ROBOCOP
-
 GARBAGE += \
   AndroidManifest.xml \
-  $(robocop-deps) \
-  $(testconstants-dep) \
   $(NULL)
 
 JAVAFILES += \
   $(java-harness) \
   $(java-tests) \
-  $(robocop-deps) \
-  $(testconstants-dep) \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 tools:: $(ANDROID_APK_NAME).apk
 
 GENERATED_DIRS += $(dir-tests)
 
--- a/build/mobile/robocop/moz.build
+++ b/build/mobile/robocop/moz.build
@@ -13,8 +13,28 @@ main.recursive_make_targets += [OBJDIR +
 main.extra_jars += [SRCDIR + '/robotium-solo-4.3.1.jar']
 main.assets = TOPSRCDIR + '/mobile/android/base/tests/assets'
 main.referenced_projects += ['Fennec']
 
 main.add_classpathentry('harness', SRCDIR,
     dstdir='harness/org/mozilla/gecko')
 main.add_classpathentry('src', TOPSRCDIR + '/mobile/android/base/tests',
     dstdir='src/org/mozilla/gecko/tests')
+
+base = '/mobile/android/base/tests/'
+TEST_HARNESS_FILES.testing.mochitest += [
+    base + 'robocop.ini',
+    base + 'robocop_autophone.ini',
+]
+TEST_HARNESS_FILES.testing.mochitest.tests.robocop += [base + x for x in [
+    '*.html',
+    '*.jpg',
+    '*.mp4',
+    '*.ogg',
+    '*.sjs',
+    '*.swf',
+    '*.webm',
+    '*.xml',
+    'robocop*.js',
+    'test*.js',
+]]
+# The ** below preserves directory structure.
+TEST_HARNESS_FILES.testing.mochitest.tests.robocop.reader_mode_pages += [base + 'reader_mode_pages/**']
--- a/docshell/base/AutoTimelineMarker.h
+++ b/docshell/base/AutoTimelineMarker.h
@@ -61,13 +61,16 @@ public:
   }
 
   ~AutoTimelineMarker()
   {
     if (mDocShell) {
       mDocShell->AddProfileTimelineMarker(mName, TRACING_INTERVAL_END);
     }
   }
+
+  AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete;
+  void operator=(const AutoTimelineMarker& aOther) = delete;
 };
 
 } // namespace mozilla
 
 #endif /* AutoTimelineMarker_h__ */
copy from dom/animation/test/css-animations/test_animation-cancel.html
copy to dom/animation/test/css-animations/file_animation-cancel.html
--- a/dom/animation/test/css-animations/test_animation-cancel.html
+++ b/dom/animation/test/css-animations/file_animation-cancel.html
@@ -1,26 +1,24 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes translateAnim {
   to { transform: translate(100px) }
 }
 @keyframes marginLeftAnim {
   to { margin-left: 100px }
 }
 @keyframes marginLeftAnim100To200 {
   from { margin-left: 100px }
   to { margin-left: 200px }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t, { style: 'animation: translateAnim 100s' });
 
   var animation = div.getAnimations()[0];
   animation.ready.then(t.step_func(function() {
@@ -155,10 +153,12 @@ test(function(t) {
   div.style.animationPlayState = 'running';
   assert_equals(animation.playState, 'idle',
                 'Animation is still idle after re-setting'
                 + ' animation-play-state: running');
 
 }, 'After cancelling an animation, updating animation-play-state doesn\'t'
    + ' make it live again');
 
+done();
 </script>
+</body>
 </html>
copy from dom/animation/test/css-animations/test_animation-currenttime.html
copy to dom/animation/test/css-animations/file_animation-currenttime.html
--- a/dom/animation/test/css-animations/test_animation-currenttime.html
+++ b/dom/animation/test/css-animations/file_animation-currenttime.html
@@ -13,22 +13,19 @@
 }
 
 @keyframes anim {
   from { margin-left: 100px; }
   to { margin-left: 200px; }
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -540,11 +537,12 @@ test(function(t) {
   div.style.animation = 'anim 100s';
 
   var animation = div.getAnimations()[0];
   animation.cancel();
   assert_equals(animation.currentTime, null,
                 'The currentTime of a cancelled animation should be null');
 }, 'Animation.currentTime after cancelling');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-animations/test_animation-finish.html
copy to dom/animation/test/css-animations/file_animation-finish.html
--- a/dom/animation/test/css-animations/test_animation-finish.html
+++ b/dom/animation/test/css-animations/file_animation-finish.html
@@ -1,26 +1,24 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 
 .animated-div {
   margin-left: 10px;
 }
 
 @keyframes anim {
   from { margin-left: 100px; }
   to { margin-left: 200px; }
 }
 
 </style>
+<body>
 <script>
 
 'use strict';
 
 const ANIM_PROP_VAL = 'anim 100s';
 const ANIM_DURATION = 100000; // ms
 
 test(function(t) {
@@ -152,9 +150,11 @@ async_test(function(t) {
     var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
     assert_equals(marginLeft, 10,
                   'The computed style should be reset when finish() is ' +
                   'called');
     t.done();
   }));
 }, 'Test resetting of computed style');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-finished.html
copy to dom/animation/test/css-animations/file_animation-finished.html
--- a/dom/animation/test/css-animations/test_animation-finished.html
+++ b/dom/animation/test/css-animations/file_animation-finished.html
@@ -1,20 +1,18 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes abc {
   to { transform: translate(10px) }
 }
 @keyframes def {}
 </style>
+<body>
 <script>
 'use strict';
 
 const ANIM_PROP_VAL = 'abc 100s';
 const ANIM_DURATION = 100000; // ms
 
 async_test(function(t) {
   var div = addDiv(t);
@@ -343,9 +341,11 @@ async_test(function(t) {
                   '"running" on finished animation');
     assert_equals(animation.currentTime, ANIM_DURATION,
                   'currentTime should not change when animation-play-state ' +
                   'changes to "running" on finished animation');
     t.done();
   }));
 }, 'Test finished promise changes when animationPlayState set to running');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-pausing.html
copy to dom/animation/test/css-animations/file_animation-pausing.html
--- a/dom/animation/test/css-animations/test_animation-pausing.html
+++ b/dom/animation/test/css-animations/file_animation-pausing.html
@@ -1,20 +1,18 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim { 
   0% { margin-left: 0px }
   100% { margin-left: 10000px }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 function getMarginLeft(cs) {
   return parseFloat(cs.marginLeft);
 }
 
 async_test(function(t) {
@@ -194,9 +192,11 @@ async_test(function(t) {
     // The ready promise should now be resolved. If it's not then test will
     // probably time out before anything else happens that causes it to resolve.
     return animation.ready;
   })).then(t.step_func(function() {
     t.done();
   }));
 }, 'Setting the current time cancels a pending pause');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-playstate.html
copy to dom/animation/test/css-animations/file_animation-playstate.html
--- a/dom/animation/test/css-animations/test_animation-playstate.html
+++ b/dom/animation/test/css-animations/file_animation-playstate.html
@@ -1,17 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim { }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   var cs = window.getComputedStyle(div);
   div.style.animation = 'anim 1000s';
 
@@ -81,9 +79,11 @@ test(function(t) {
 
   var animation = div.getAnimations()[0];
   animation.cancel();
   animation.currentTime = 50 * 1000;
   assert_equals(animation.playState, 'paused',
                 'After seeking an idle animation, it is effectively paused');
 }, 'After cancelling an animation, seeking it makes it paused');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-ready.html
copy to dom/animation/test/css-animations/file_animation-ready.html
--- a/dom/animation/test/css-animations/test_animation-ready.html
+++ b/dom/animation/test/css-animations/file_animation-ready.html
@@ -1,19 +1,17 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes abc {
   to { transform: translate(10px) }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
 
@@ -243,9 +241,11 @@ async_test(function(t) {
     assert_equals(resolvedAnimation, animation,
                   'Promise received when ready Promise for a pause operation'
                   + ' is completed is the animation on which the pause was'
                   + ' performed');
     t.done();
   }));
 }, 'When a pause is complete the Promise callback gets the correct animation');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-starttime.html
copy to dom/animation/test/css-animations/file_animation-starttime.html
--- a/dom/animation/test/css-animations/test_animation-starttime.html
+++ b/dom/animation/test/css-animations/file_animation-starttime.html
@@ -13,22 +13,19 @@
 }
 
 @keyframes anim {
   from { margin-left: 100px; }
   to { margin-left: 200px; }
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -549,11 +546,12 @@ async_test(function(t) {
   animation.ready.then(t.step_func(function() {
     animation.cancel();
     assert_equals(animation.startTime, null,
                   'The startTime of a cancelled animation should be null');
     t.done();
   }));
 }, 'Animation.startTime after cancelling');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-animations/test_animations-dynamic-changes.html
copy to dom/animation/test/css-animations/file_animations-dynamic-changes.html
--- a/dom/animation/test/css-animations/test_animations-dynamic-changes.html
+++ b/dom/animation/test/css-animations/file_animations-dynamic-changes.html
@@ -1,20 +1,18 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim1 {
   to { left: 100px }
 }
 @keyframes anim2 { }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim1 100s';
 
   var originalAnimation = div.getAnimations()[0];
@@ -151,9 +149,11 @@ async_test(function(t) {
     assert_true(animations[1].startTime > animations[0].startTime,
                 'Interleaved animation starts later than existing animations');
     assert_true(animations[0].startTime > animations[2].startTime,
                 'Original animations retain their start time');
     t.done();
   }));
 }, 'Animation state is preserved when interleaving animations in list');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_effect-name.html
copy to dom/animation/test/css-animations/file_effect-name.html
--- a/dom/animation/test/css-animations/test_effect-name.html
+++ b/dom/animation/test/css-animations/file_effect-name.html
@@ -1,19 +1,17 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes xyz {
   to { left: 100px }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'xyz 100s';
   assert_equals(div.getAnimations()[0].effect.name, 'xyz',
                 'Animation effect name matches keyframes rule name');
@@ -29,9 +27,11 @@ test(function(t) {
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'x\\79 z 100s';
   assert_equals(div.getAnimations()[0].effect.name, 'xyz',
                 'Hex-escaped animation name matches keyframes rule'
                 + ' name');
 }, 'Animation name with hex-escape');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_effect-target.html
copy to dom/animation/test/css-animations/file_effect-target.html
--- a/dom/animation/test/css-animations/test_effect-target.html
+++ b/dom/animation/test/css-animations/file_effect-target.html
@@ -1,21 +1,21 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim { }
 </style>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim 100s';
   var animation = div.getAnimations()[0];
   assert_equals(animation.effect.target, div,
     'Animation.target is the animatable div');
 }, 'Returned CSS animations have the correct Animation.target');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_element-get-animations.html
copy to dom/animation/test/css-animations/file_element-get-animations.html
--- a/dom/animation/test/css-animations/test_element-get-animations.html
+++ b/dom/animation/test/css-animations/file_element-get-animations.html
@@ -1,26 +1,24 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim1 {
   to { left: 100px }
 }
 @keyframes anim2 {
   to { top: 100px }
 }
 @keyframes multiPropAnim {
   to { background: green, opacity: 0.5, left: 100px, top: 100px }
 }
 @keyframes empty { }
 </style>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
   assert_equals(div.getAnimations().length, 0,
     'getAnimations returns an empty sequence for an element'
     + ' with no animations');
@@ -264,9 +262,11 @@ test(function(t) {
     'getAnimations does not return cancelled animations');
 
   animation.play();
   assert_equals(div.getAnimations().length, 1,
     'getAnimations returns cancelled animations that have been re-started');
 
 }, 'getAnimations for CSS Animations that are cancelled');
 
+done();
 </script>
+</body>
--- a/dom/animation/test/css-animations/test_animation-cancel.html
+++ b/dom/animation/test/css-animations/test_animation-cancel.html
@@ -1,164 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes translateAnim {
-  to { transform: translate(100px) }
-}
-@keyframes marginLeftAnim {
-  to { margin-left: 100px }
-}
-@keyframes marginLeftAnim100To200 {
-  from { margin-left: 100px }
-  to { margin-left: 200px }
-}
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: translateAnim 100s' });
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(getComputedStyle(div).transform, 'none',
-                      'transform style is animated before cancelling');
-    animation.cancel();
-    assert_equals(getComputedStyle(div).transform, 'none',
-                  'transform style is no longer animated after cancelling');
-    t.done();
-  }));
-}, 'Animated style is cleared after cancelling a running CSS animation');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: translateAnim 100s forwards' });
-
-  var animation = div.getAnimations()[0];
-  animation.finish();
-
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(getComputedStyle(div).transform, 'none',
-                      'transform style is filling before cancelling');
-    animation.cancel();
-    assert_equals(getComputedStyle(div).transform, 'none',
-                  'fill style is cleared after cancelling');
-    t.done();
-  }));
-}, 'Animated style is cleared after cancelling a filling CSS animation');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: translateAnim 100s' });
-  var animation = div.getAnimations()[0];
-
-  div.addEventListener('animationend', t.step_func(function() {
-    assert_unreached('Got unexpected end event on cancelled animation');
-  }));
-
-  animation.ready.then(t.step_func(function() {
-    // Seek to just before the end then cancel
-    animation.currentTime = 99.9 * 1000;
-    animation.cancel();
-
-    // Then wait a couple of frames and check that no event was dispatched
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Cancelled CSS animations do not dispatch events');
-
-test(function(t) {
-  var div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' });
-
-  var animation = div.getAnimations()[0];
-
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is not animated after cancelling');
-
-  animation.currentTime = 50 * 1000;
-  assert_equals(getComputedStyle(div).marginLeft, '50px',
-                'margin-left style is updated when cancelled animation is'
-                + ' seeked');
-}, 'After cancelling an animation, it can still be seeked');
-
-async_test(function(t) {
-  var div =
-    addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, '0px',
-                  'margin-left style is not animated after cancelling');
-    animation.play();
-    assert_equals(getComputedStyle(div).marginLeft, '100px',
-                  'margin-left style is animated after re-starting animation');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Animation succeeds in running after being re-started');
-    t.done();
-  }));
-}, 'After cancelling an animation, it can still be re-used');
-
-test(function(t) {
-  var div =
-    addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is not animated after cancelling');
-
-  // Trigger a change to some animation properties and check that this
-  // doesn't cause the animation to become live again
-  div.style.animationDuration = '200s';
-  flushComputedStyle(div);
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is still not animated after updating'
-                + ' animation-duration');
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after updating animation-duration');
-}, 'After cancelling an animation, updating animation properties doesn\'t make'
-   + ' it live again');
-
-test(function(t) {
-  var div =
-    addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is not animated after cancelling');
-
-  // Make some changes to animation-play-state and check that the
-  // animation doesn't become live again. This is because it should be
-  // possible to cancel an animation from script such that all future
-  // changes to style are ignored.
-
-  // Redundant change
-  div.style.animationPlayState = 'running';
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after a redundant change to'
-                + ' animation-play-state');
-
-  // Pause
-  div.style.animationPlayState = 'paused';
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after setting'
-                + ' animation-play-state: paused');
-
-  // Play
-  div.style.animationPlayState = 'running';
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after re-setting'
-                + ' animation-play-state: running');
-
-}, 'After cancelling an animation, updating animation-play-state doesn\'t'
-   + ' make it live again');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-cancel.html");
+  });
 </script>
 </html>
--- a/dom/animation/test/css-animations/test_animation-currenttime.html
+++ b/dom/animation/test/css-animations/test_animation-currenttime.html
@@ -1,550 +1,15 @@
 <!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests for the effect of setting a CSS animation's
-           Animation.currentTime</title>
-    <style>
-
-.animated-div {
-  margin-left: 10px;
-  /* Make it easier to calculate expected values: */
-  animation-timing-function: linear ! important;
-}
-
-@keyframes anim {
-  from { margin-left: 100px; }
-  to { margin-left: 200px; }
-}
-
-    </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="../testcommon.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
 'use strict';
-
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
-// TODO: Once the computedTiming property is implemented, add checks to the
-// checker helpers to ensure that computedTiming's properties are updated as
-// expected.
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
-
-
-const CSS_ANIM_EVENTS =
-  ['animationstart', 'animationiteration', 'animationend'];
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
-
-/**
- * These helpers get the value that the currentTime needs to be set to, to put
- * an animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into
- * the middle of various phases or points through the active duration.
- */
-function currentTimeForBeforePhase(timeline) {
-  return ANIM_DELAY_MS / 2;
-}
-function currentTimeForActivePhase(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS / 2;
-}
-function currentTimeForAfterPhase(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2;
-}
-function currentTimeForStartOfActiveInterval(timeline) {
-  return ANIM_DELAY_MS;
-}
-function currentTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5;
-}
-function currentTimeForEndOfActiveInterval(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const UNANIMATED_POSITION = 10;
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when currentTime is set to zero (the beginning of the start delay).
-function checkStateOnSettingCurrentTimeToZero(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the start delay');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the start delay');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'at the beginning of the start delay');
-}
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  // the 0.0001 here is for rounding error
-  assert_less_than_equal(animation.currentTime,
-    animation.timeline.currentTime - animation.startTime + 0.0001,
-    'Animation.currentTime should be less than the local time ' +
-    'equivalent of the timeline\'s currentTime on the first paint tick ' +
-    'after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" on the first paint tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when currentTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when currentTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, "running",
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"finished" at the end of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-    'the computed value of margin-left should be unaffected ' +
-    'by the animation at the end of the active duration when the ' +
-    'animation-fill-mode is none');
-}
-
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  // Animations shouldn't start until the next paint tick, so:
-  assert_equals(animation.currentTime, 0,
-    'Animation.currentTime should be zero when an animation ' +
-    'is initially created');
-
-  assert_equals(animation.playState, "pending",
-    'Animation.playState should be "pending" when an animation ' +
-    'is initially created');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" when an animation is initially created');
-
-  // XXX Ideally we would have a test to check the ready Promise is initially
-  // unresolved, but currently there is no Web API to do that. Waiting for the
-  // ready Promise with a timeout doesn't work because the resolved callback
-  // will be called (async) regardless of whether the Promise was resolved in
-  // the past or is resolved in the future.
-
-  // So that animation is running instead of paused when we set currentTime:
-  animation.startTime = animation.timeline.currentTime;
-
-  assert_approx_equals(animation.currentTime, 0, 0.0001, // rounding error
-    'Check setting of currentTime actually works');
-
-  checkStateOnSettingCurrentTimeToZero(animation);
-}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
-   'currentTime');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-
-    animation.currentTime =
-      currentTimeForStartOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.currentTime =
-      currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.currentTime =
-      currentTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-currenttime.html");
   });
-}, 'Skipping forward through animation');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  // So that animation is running instead of paused when we set currentTime:
-  animation.startTime = animation.timeline.currentTime;
-
-  animation.currentTime = currentTimeForEndOfActiveInterval(animation.timeline);
-
-  var previousTimelineTime = animation.timeline.currentTime;
-
-  // Skipping over the active interval will dispatch an 'animationstart' then
-  // an 'animationend' event. We need to wait for these events before we start
-  // testing going backwards since EventWatcher will fail the test if it gets
-  // an event that we haven't told it about.
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(t.step_func(function() {
-    assert_true(document.timeline.currentTime - previousTimelineTime <
-                  ANIM_DUR_MS,
-                'Sanity check that seeking worked rather than the events ' +
-                'firing after normal playback through the very long ' +
-                'animation duration');
-
-    // Now we can start the tests for skipping backwards, but first we check
-    // that after the events we're still in the same end time state:
-    checkStateAtActiveIntervalEndTime(animation);
-
-    animation.currentTime =
-      currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-
-    // Despite going backwards from after the end of the animation (to being
-    // in the active interval), we now expect an 'animationstart' event
-    // because the animation should go from being inactive to active.
-    //
-    // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
-    // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.wait_for('animationstart');
-    checkStateAtFiftyPctOfActiveInterval(animation);
-    return promise;
-  })).then(t.step_func(function() {
-    animation.currentTime =
-      currentTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.currentTime = 0;
-    // Despite going backwards from just after the active interval starts to
-    // the animation start time, we now expect an animationend event
-    // because we went from inside to outside the active interval.
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-
-  // This must come after we've set up the Promise chain, since requesting
-  // computed style will force events to be dispatched.
-  // XXX For some reason this fails occasionally (either the animation.playState
-  // check or the marginLeft check).
-  //checkStateAtActiveIntervalEndTime(animation);
-}, 'Skipping backwards through animation');
-
-
-// Next we have multiple tests to check that redundant currentTime changes do
-// NOT dispatch events. It's impossible to distinguish between events not being
-// dispatched and events just taking an incredibly long time to dispatch
-// without waiting an infinitely long time. Obviously we don't want to do that
-// (block this test from finishing forever), so instead we just listen for
-// events until two animation frames (i.e. requestAnimationFrame callbacks)
-// have happened, then assume that no events will ever be dispatched for the
-// redundant changes if no events were detected in that time.
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-  animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> active, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-  animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> active, then back');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var exception;
-    try {
-      animation.currentTime = null;
-    } catch (e) {
-      exception = e;
-    }
-    assert_equals(exception.name, 'TypeError',
-      'Expect TypeError exception on trying to set ' +
-      'Animation.currentTime to null');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Setting currentTime to null');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-  var pauseTime;
-
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(animation.currentTime, null,
-      'Animation.currentTime not null on ready Promise resolve');
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    pauseTime = animation.currentTime;
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(animation.currentTime, pauseTime,
-      'Animation.currentTime is unchanged after pausing');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Animation.currentTime after pausing');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    // just before animation ends:
-    animation.currentTime = ANIM_DELAY_MS + ANIM_DUR_MS - 1;
-
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
-      'Animation.currentTime should not continue to increase after the ' +
-      'animation has finished');
-    t.done();
-  }));
-}, 'Animation.currentTime clamping');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    // play backwards:
-    animation.playbackRate = -1;
-
-    // just before animation ends (at the "start"):
-    animation.currentTime = 1;
-
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    assert_equals(animation.currentTime, 0,
-      'Animation.currentTime should not continue to decrease after an ' +
-      'animation running in reverse has finished and currentTime is zero');
-    t.done();
-  }));
-}, 'Animation.currentTime clamping for reversed animation');
-
-test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(animation.currentTime, null,
-                'The currentTime of a cancelled animation should be null');
-}, 'Animation.currentTime after cancelling');
-
-    </script>
-  </body>
+</script>
 </html>
--- a/dom/animation/test/css-animations/test_animation-finish.html
+++ b/dom/animation/test/css-animations/test_animation-finish.html
@@ -1,160 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-
-.animated-div {
-  margin-left: 10px;
-}
-
-@keyframes anim {
-  from { margin-left: 100px; }
-  to { margin-left: 200px; }
-}
-
-</style>
 <script>
-
 'use strict';
-
-const ANIM_PROP_VAL = 'anim 100s';
-const ANIM_DURATION = 100000; // ms
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.playbackRate = 0;
-
-  var threw = false;
-  try {
-    animation.finish();
-  } catch (e) {
-    threw = true;
-    assert_equals(e.name, 'InvalidStateError',
-                  'Exception should be an InvalidStateError exception when ' +
-                  'trying to finish an animation with playbackRate == 0');
-  }
-  assert_true(threw,
-              'Expect InvalidStateError exception trying to finish an ' +
-              'animation with playbackRate == 0');
-}, 'Test exceptions when finishing non-running animation');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  div.style.animationIterationCount = 'infinite';
-  var animation = div.getAnimations()[0];
-
-  var threw = false;
-  try {
-    animation.finish();
-  } catch (e) {
-    threw = true;
-    assert_equals(e.name, 'InvalidStateError',
-                  'Exception should be an InvalidStateError exception when ' +
-                  'trying to finish an infinite animation');
-  }
-  assert_true(threw,
-              'Expect InvalidStateError exception trying to finish an ' +
-              'infinite animation');
-}, 'Test exceptions when finishing infinite animation');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.finish();
-  assert_equals(animation.currentTime, ANIM_DURATION,
-                'After finishing, the currentTime should be set to the end ' +
-                'of the active duration');
-}, 'Test finishing of animation');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION + 1000; // 1s past effect end
-
-  animation.finish();
-  assert_equals(animation.currentTime, ANIM_DURATION,
-                'After finishing, the currentTime should be set back to the ' +
-                'end of the active duration');
-}, 'Test finishing of animation with a current time past the effect end');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.playbackRate = -1;
-    animation.finish();
-    assert_equals(animation.currentTime, 0,
-                  'After finishing a reversed animation the currentTime ' +
-                  'should be set to zero');
-    t.done();
-  }));
-}, 'Test finishing of reversed animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.playbackRate = -1;
-
-    animation.currentTime = -1000;
-
-    animation.finish();
-    assert_equals(animation.currentTime, 0,
-                  'After finishing a reversed animation the currentTime ' +
-                  'should be set back to zero');
-    t.done();
-  }));
-}, 'Test finishing of reversed animation with with a current time less ' +
-   'than zero');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.pause();
-
-  animation.ready.then(t.step_func(function() {
-    animation.finish();
-    assert_equals(animation.playState, 'paused',
-                  'The play state of a paused animation should remain ' +
-                  '"paused" even after finish() is called');
-    t.done();
-  }));
-}, 'Test paused state after finishing of animation');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    animation.finish();
-    var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-    assert_equals(marginLeft, 10,
-                  'The computed style should be reset when finish() is ' +
-                  'called');
-    t.done();
-  }));
-}, 'Test resetting of computed style');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-finish.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-finished.html
+++ b/dom/animation/test/css-animations/test_animation-finished.html
@@ -1,351 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes abc {
-  to { transform: translate(10px) }
-}
-@keyframes def {}
-</style>
 <script>
 'use strict';
-
-const ANIM_PROP_VAL = 'abc 100s';
-const ANIM_DURATION = 100000; // ms
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same object when playing starts');
-    animation.pause();
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise does not change when pausing');
-    animation.play();
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise does not change when play() unpauses');
-
-    animation.currentTime = ANIM_DURATION;
-
-    return animation.finished;
-  })).then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same object when playing completes');
-    t.done();
-  }));
-}, 'Test pausing then playing does not change the finished promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same object when playing completes');
-    animation.play();
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise changes when replaying animation');
-
-    previousFinishedPromise = animation.finished;
-    animation.play();
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same after redundant play() call');
-
-    t.done();
-  }));
-}, 'Test restarting a finished animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    previousFinishedPromise = animation.finished;
-    animation.playbackRate = -1;
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should be replaced when reversing a ' +
-                      'finished promise');
-    animation.currentTime = 0;
-    return animation.finished;
-  })).then(t.step_func(function() {
-    previousFinishedPromise = animation.finished;
-    animation.play();
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise is replaced after play() call on ' +
-                      'finished, reversed animation');
-    t.done();
-  }));
-}, 'Test restarting a reversed finished animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.currentTime = ANIM_DURATION + 1000;
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is unchanged jumping past end of ' +
-                  'finished animation');
-
-    t.done();
-  }));
-}, 'Test redundant finishing of animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION;
-  animation.finished.then(t.step_func(function(resolvedAnimation) {
-    assert_equals(resolvedAnimation, animation,
-                  'Object identity of animation passed to Promise callback'
-                  + ' matches the animation object owning the Promise');
-    t.done();
-  }));
-}, 'The finished promise is fulfilled with its Animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up pending animation
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  var previousFinishedPromise = animation.finished;
-
-  // Set up listeners on finished promise
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise is fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after the original is ' +
-                      'rejected');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now cancel the animation and flush styles
-  div.style.animation = '';
-  window.getComputedStyle(div).animation;
-
-}, 'finished promise is rejected when an animation is cancelled by resetting ' +
-   'the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // As before, but this time instead of removing all animations, simply update
-  // the list of animations. At least for Firefox, updating is a different
-  // code path.
-
-  // Set up pending animation
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  var previousFinishedPromise = animation.finished;
-
-  // Set up listeners on finished promise
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after the original is ' +
-                      'rejected');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now update the animation and flush styles
-  div.style.animation = 'def 100s';
-  window.getComputedStyle(div).animation;
-
-}, 'finished promise is rejected when an animation is cancelled by changing ' +
-   'the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  var previousFinishedPromise = animation.finished;
-
-  // Set up listeners on finished promise
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after the original is ' +
-                      'rejected');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  animation.cancel();
-
-}, 'finished promise is rejected when an animation is cancelled by calling ' +
-   'cancel()');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.cancel();
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'A new finished promise should be created when'
-                      + ' cancelling a finished player');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'cancelling an already-finished player replaces the finished promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-
-  // The spec says we still create a new finished promise and reject the old
-  // one even if we're already idle. That behavior might change, but for now
-  // test that we do that.
-  animation.finished.catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    t.done();
-  }));
-
-  // Redundant call to cancel();
-  var previousFinishedPromise = animation.finished;
-  animation.cancel();
-  assert_not_equals(animation.finished, previousFinishedPromise,
-                    'A redundant call to cancel() should still generate a new'
-                    + ' finished promise');
-}, 'cancelling an idle player still replaces the finished promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  const HALF_DUR = ANIM_DURATION / 2;
-  const QUARTER_DUR = ANIM_DURATION / 4;
-
-  animation.currentTime = HALF_DUR;
-  div.style.animationDuration = QUARTER_DUR + 'ms';
-  // Animation should now be finished
-
-  // Below we use gotNextFrame to check that shortening of the animation
-  // duration causes the finished promise to resolve, rather than it just
-  // getting resolved on the next animation frame. This relies on the fact
-  // that the promises are resolved as a micro-task before the next frame
-  // happens.
-
-  window.getComputedStyle(div).animationDuration; // flush style
-  var gotNextFrame = false;
-  waitForFrame().then(function() {
-    gotNextFrame = true;
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-finished.html");
   });
-
-  animation.finished.then(t.step_func(function() {
-    assert_false(gotNextFrame, 'shortening of the animation duration should ' +
-                               'resolve the finished promise');
-    assert_equals(animation.currentTime, HALF_DUR,
-                  'currentTime should be unchanged when duration shortened');
-    var previousFinishedPromise = animation.finished;
-    div.style.animationDuration = ANIM_DURATION + 'ms'; // now active again
-    window.getComputedStyle(div).animationDuration; // flush style
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after lengthening the ' +
-                      'duration causes the animation to become active');
-    t.done();
-  }));
-}, 'Test finished promise changes for animation duration changes');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    animation.playbackRate = 0;
-    animation.currentTime = ANIM_DURATION + 1000;
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise should not resolve when playbackRate ' +
-                     'is zero');
-  }));
-}, 'Test finished promise changes when playbackRate == 0');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    animation.playbackRate = -1;
-    return animation.finished;
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Test finished promise resolves when playbackRate set to a negative value');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(function() {
-    div.style.animationPlayState = 'running';
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Should not replay when animation-play-state changes to ' +
-                  '"running" on finished animation');
-    assert_equals(animation.currentTime, ANIM_DURATION,
-                  'currentTime should not change when animation-play-state ' +
-                  'changes to "running" on finished animation');
-    t.done();
-  }));
-}, 'Test finished promise changes when animationPlayState set to running');
-
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-pausing.html
+++ b/dom/animation/test/css-animations/test_animation-pausing.html
@@ -1,202 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim { 
-  0% { margin-left: 0px }
-  100% { margin-left: 10000px }
-}
-</style>
 <script>
 'use strict';
-
-function getMarginLeft(cs) {
-  return parseFloat(cs.marginLeft);
-}
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(getMarginLeft(cs) > previousAnimVal,
-                'margin-left is initially increasing');
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    previousAnimVal = getMarginLeft(cs);
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'margin-left does not increase after calling pause()');
-    t.done();
-  }));
-}, 'pause() a running animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  animation.pause();
-  div.style.animationPlayState = 'running';
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'running',
-                  'animation-play-state is running');
-    assert_equals(getMarginLeft(cs), 0,
-                  'Paused value of margin-left is zero');
-    t.done();
-  }));
-}, 'pause() overrides animation-play-state');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  animation.play();
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(getMarginLeft(cs) > 0,
-                'Playing value of margin-left is greater than zero');
-    t.done();
-  }));
-}, 'play() overrides animation-play-state');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  animation.play();
-
-  var previousAnimVal;
-
-  animation.ready.then(function() {
-    div.style.animationPlayState = 'running';
-    cs.animationPlayState; // Trigger style resolution
-    return waitForFrame();
-  }).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'running',
-                  'animation-play-state is running');
-    div.style.animationPlayState = 'paused';
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'paused',
-                  'animation-play-state is paused');
-    previousAnimVal = getMarginLeft(cs);
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'Animated value of margin-left does not change when'
-                  + ' paused by style');
-    t.done();
-  }));
-}, 'play() is overridden by later setting "animation-play-state: paused"');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  // Set the specified style first. If implementations fail to
-  // apply the style changes first, they will ignore the redundant
-  // call to play() and fail to correctly override the pause style.
-  div.style.animationPlayState = 'paused';
-  animation.play();
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'paused',
-                  'animation-play-state is paused');
-    assert_true(getMarginLeft(cs) > previousAnimVal,
-                'Playing value of margin-left is increasing');
-    t.done();
-  }));
-}, 'play() flushes pending changes to animation-play-state first');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  // Unlike the previous test for play(), since calling pause() is sticky,
-  // we'll apply it even if the underlying style also says we're paused.
-  //
-  // We would like to test that implementations flush styles before running
-  // pause() but actually there's no style we can currently set that will
-  // change the behavior of pause(). That may change in the future
-  // (e.g. if we introduce animation-timeline or animation-playback-rate etc.).
-  //
-  // For now this just serves as a sanity check that we do the same thing
-  // even if we set style before calling the API.
-  div.style.animationPlayState = 'running';
-  animation.pause();
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'running',
-                  'animation-play-state is running');
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'Paused value of margin-left does not change');
-    t.done();
-  }));
-}, 'pause() applies pending changes to animation-play-state first');
-// (Note that we can't actually test for this; see comment above, in test-body.)
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  var animation = div.getAnimations()[0];
-
-  var readyPromiseRun = false;
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    assert_equals(animation.playState, 'pending', 'Animation is pause pending');
-
-    // Set current time
-    animation.currentTime = 5000;
-    assert_equals(animation.playState, 'running',
-                  'Animation is running immediately after setting currentTime');
-
-    // The ready promise should now be resolved. If it's not then test will
-    // probably time out before anything else happens that causes it to resolve.
-    return animation.ready;
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Setting the current time cancels a pending pause');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-pausing.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-playstate.html
+++ b/dom/animation/test/css-animations/test_animation-playstate.html
@@ -1,89 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim { }
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'running');
-    t.done();
-  }));
-}, 'Animation returns correct playState when running');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'paused');
-    t.done();
-  }));
-}, 'Animation returns correct playState when paused');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  animation.pause();
-  assert_equals(animation.playState, 'pending');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'paused');
-    t.done();
-  }));
-}, 'Animation.playState updates when paused by script');
-
-test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  div.style.animationPlayState = 'running';
-  // This test also checks that calling playState flushes style
-  assert_equals(animation.playState, 'pending',
-                'Animation.playState reports pending after updating'
-                + ' animation-play-state (got: ' + animation.playState + ')');
-}, 'Animation.playState updates when resumed by setting style');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(animation.playState, 'idle');
-}, 'Animation returns correct playState when cancelled');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  animation.currentTime = 50 * 1000;
-  assert_equals(animation.playState, 'paused',
-                'After seeking an idle animation, it is effectively paused');
-}, 'After cancelling an animation, seeking it makes it paused');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-playstate.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-ready.html
+++ b/dom/animation/test/css-animations/test_animation-ready.html
@@ -1,251 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes abc {
-  to { transform: translate(10px) }
-}
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  var originalReadyPromise = animation.ready;
-  var pauseReadyPromise;
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.ready, originalReadyPromise,
-                  'Ready promise is the same object when playing completes');
-    animation.pause();
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'A new ready promise is created when pausing');
-    pauseReadyPromise = animation.ready;
-    // Wait for the promise to fulfill since if we abort the pause the ready
-    // promise object is reused.
-    return animation.ready;
-  })).then(t.step_func(function() {
-    animation.play();
-    assert_not_equals(animation.ready, pauseReadyPromise,
-                      'A new ready promise is created when playing');
-    t.done();
-  }));
-}, 'A new ready promise is created when play()/pause() is called');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s paused';
-  var animation = div.getAnimations()[0];
-
-  var originalReadyPromise = animation.ready;
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'running';
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'After updating animation-play-state a new ready promise'
-                      + ' object is created');
-    t.done();
-  }));
-}, 'A new ready promise is created when setting animation-play-state: running');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var promiseBeforeCallingPlay = animation.ready;
-    animation.play();
-    assert_equals(animation.ready, promiseBeforeCallingPlay,
-                  'Ready promise has same object identity after redundant call'
-                  + ' to play()');
-    t.done();
-  }));
-}, 'Redundant calls to play() do not generate new ready promise objects');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function(resolvedAnimation) {
-    assert_equals(resolvedAnimation, animation,
-                  'Object identity of Animation passed to Promise callback'
-                  + ' matches the Animation object owning the Promise');
-    t.done();
-  }));
-}, 'The ready promise is fulfilled with its Animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up pending animation
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending',
-               'Animation is initially pending');
-
-  // Set up listeners on ready promise
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise is fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now cancel the animation and flush styles
-  div.style.animation = '';
-  window.getComputedStyle(div).animation;
-
-}, 'ready promise is rejected when an animation is cancelled by resetting'
-   + ' the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // As before, but this time instead of removing all animations, simply update
-  // the list of animations. At least for Firefox, updating is a different
-  // code path.
-
-  // Set up pending animation
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending',
-                'Animation is initially pending');
-
-  // Set up listeners on ready promise
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now update the animation and flush styles
-  div.style.animation = 'def 100s';
-  window.getComputedStyle(div).animation;
-
-}, 'ready promise is rejected when an animation is cancelled by updating'
-   + ' the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  animation.cancel();
-}, 'ready promise is rejected when a play-pending animation is cancelled by'
-   + ' calling cancel()');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    animation.pause();
-
-    // Set up listeners on pause-pending ready promise
-    animation.ready.then(t.step_func(function() {
-      assert_unreached('ready promise was fulfilled');
-    })).catch(t.step_func(function(err) {
-      assert_equals(err.name, 'AbortError',
-                    'ready promise is rejected with AbortError');
-    })).then(t.step_func(function() {
-      t.done();
-    }));
-
-    animation.cancel();
-  }));
-}, 'ready promise is rejected when a pause-pending animation is cancelled by'
-   + ' calling cancel()');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  var originalReadyPromise = animation.ready;
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'A new Promise object is generated when setting'
-                      + ' animation-play-state: paused');
-    t.done();
-  }));
-}, 'A new ready promise is created when setting animation-play-state: paused');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    var firstReadyPromise = animation.ready;
-    animation.pause();
-    assert_equals(animation.ready, firstReadyPromise,
-                  'Ready promise objects are identical after redundant pause');
-    t.done();
-  }));
-}, 'Pausing twice re-uses the same Promise');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-
-    // Flush style and verify we're pending at the same time
-    assert_equals(animation.playState, 'pending', 'Animation is pending');
-    var pauseReadyPromise = animation.ready;
-
-    // Now play again immediately
-    div.style.animationPlayState = 'running';
-    assert_equals(animation.playState, 'pending', 'Animation is still pending');
-    assert_equals(animation.ready, pauseReadyPromise,
-                  'The pause Promise is re-used when playing while waiting'
-                  + ' to pause');
-
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Animation is running after aborting a pause');
-    t.done();
-  }));
-}, 'If a pause operation is interrupted, the ready promise is reused');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    return animation.ready;
-  })).then(t.step_func(function(resolvedAnimation) {
-    assert_equals(resolvedAnimation, animation,
-                  'Promise received when ready Promise for a pause operation'
-                  + ' is completed is the animation on which the pause was'
-                  + ' performed');
-    t.done();
-  }));
-}, 'When a pause is complete the Promise callback gets the correct animation');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-ready.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-starttime.html
+++ b/dom/animation/test/css-animations/test_animation-starttime.html
@@ -1,559 +1,15 @@
 <!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests for the effect of setting a CSS animation's
-           Animation.startTime</title>
-    <style>
-
-.animated-div {
-  margin-left: 10px;
-  /* Make it easier to calculate expected values: */
-  animation-timing-function: linear ! important;
-}
-
-@keyframes anim {
-  from { margin-left: 100px; }
-  to { margin-left: 200px; }
-}
-
-    </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="../testcommon.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
 'use strict';
-
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
-// TODO: Once the computedTiming property is implemented, add checks to the
-// checker helpers to ensure that computedTiming's properties are updated as
-// expected.
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
-
-
-const CSS_ANIM_EVENTS =
-  ['animationstart', 'animationiteration', 'animationend'];
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
-
-/**
- * These helpers get the value that the startTime needs to be set to, to put an
- * animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into the
- * middle of various phases or points through the active duration.
- */
-function startTimeForBeforePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS / 2;
-}
-function startTimeForActivePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS / 2;
-}
-function startTimeForAfterPhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS - ANIM_DELAY_MS / 2;
-}
-function startTimeForStartOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS;
-}
-function startTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS * 0.5;
-}
-function startTimeForEndOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const UNANIMATED_POSITION = 10;
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
-    'Animation.startTime should be less than the timeline\'s ' +
-    'currentTime on the first paint tick after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" on the first paint tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when startTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when startTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, "running",
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"finished" at the end of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-    'the computed value of margin-left should be unaffected ' +
-    'by the animation at the end of the active duration when the ' +
-    'animation-fill-mode is none');
-}
-
-test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.startTime, null, 'startTime is unresolved');
-}, 'startTime of a newly created (play-pending) animation is unresolved');
-
-test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.startTime, null, 'startTime is unresolved');
-}, 'startTime of a newly created (pause-pending) animation is unresolved');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    assert_true(animation.startTime > 0,
-                'startTime is resolved when running');
-    t.done();
-  }));
-}, 'startTime is resolved when running');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-                  'startTime is unresolved when paused');
-    t.done();
-  }));
-}, 'startTime is unresolved when paused');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    getComputedStyle(div).animationPlayState;
-    assert_not_equals(animation.startTime, null,
-                      'startTime is resolved when pause-pending');
-
-    div.style.animationPlayState = 'running';
-    getComputedStyle(div).animationPlayState;
-    assert_not_equals(animation.startTime, null,
-                      'startTime is preserved when a pause is aborted');
-    t.done();
-  }));
-}, 'startTime while pause-pending and play-pending');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  // Seek to end to put us in the finished state
-  // FIXME: Once we implement finish(), use that here.
-  animation.currentTime = 100 * 1000;
-  animation.ready.then(t.step_func(function() {
-    // Call play() which puts us back in the running state
-    animation.play();
-    // FIXME: Enable this once we implement finishing behavior (bug 1074630)
-    /*
-    assert_equals(animation.startTime, null, 'startTime is unresolved');
-    */
-    t.done();
-  }));
-}, 'startTime while play-pending from finished state');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  var animation = div.getAnimations()[0];
-
-  assert_equals(animation.startTime, null, 'The initial startTime is null');
-  var initialTimelineTime = document.timeline.currentTime;
-
-  animation.ready.then(t.step_func(function() {
-    assert_true(animation.startTime > initialTimelineTime,
-                'After the animation has started, startTime is greater than ' +
-                'the time when it was started');
-    var startTimeBeforePausing = animation.startTime;
-
-    div.style.animationPlayState = 'paused';
-    // Flush styles just in case querying animation.startTime doesn't flush
-    // styles (which would be a bug in of itself and could mask a further bug
-    // by causing startTime to appear to not change).
-    getComputedStyle(div).animationPlayState;
-
-    assert_equals(animation.startTime, startTimeBeforePausing,
-                  'The startTime does not change when pausing-pending');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-                  'After actually pausing, the startTime of an animation ' +
-                  'is null');
-    t.done();
-  }));
-}, 'Pausing should make the startTime become null');
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-  var currentTime = animation.timeline.currentTime;
-  animation.startTime = currentTime;
-  assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
-    'Check setting of startTime actually works');
-}, 'Sanity test to check round-tripping assigning to a new animation\'s ' +
-   'startTime');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-starttime.html");
   });
-}, 'Skipping forward through animation');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-
-  var previousTimelineTime = animation.timeline.currentTime;
-
-  // Skipping over the active interval will dispatch an 'animationstart' then
-  // an 'animationend' event. We need to wait for these events before we start
-  // testing going backwards since EventWatcher will fail the test if it gets
-  // an event that we haven't told it about.
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(t.step_func(function() {
-    assert_true(document.timeline.currentTime - previousTimelineTime <
-                  ANIM_DUR_MS,
-                'Sanity check that seeking worked rather than the events ' +
-                'firing after normal playback through the very long ' +
-                'animation duration');
-
-    // Now we can start the tests for skipping backwards, but first we check
-    // that after the events we're still in the same end time state:
-    checkStateAtActiveIntervalEndTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-
-    // Despite going backwards from after the end of the animation (to being
-    // in the active interval), we now expect an 'animationstart' event
-    // because the animation should go from being inactive to active.
-    //
-    // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
-    // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.wait_for('animationstart');
-    checkStateAtFiftyPctOfActiveInterval(animation);
-    return promise;
-  })).then(t.step_func(function() {
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.startTime = animation.timeline.currentTime;
-    // Despite going backwards from just after the active interval starts to
-    // the animation start time, we now expect an animationend event
-    // because we went from inside to outside the active interval.
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-
-  // This must come after we've set up the Promise chain, since requesting
-  // computed style will force events to be dispatched.
-  // XXX For some reason this fails occasionally (either the animation.playState
-  // check or the marginLeft check).
-  //checkStateAtActiveIntervalEndTime(animation);
-}, 'Skipping backwards through animation');
-
-
-// Next we have multiple tests to check that redundant startTime changes do NOT
-// dispatch events. It's impossible to distinguish between events not being
-// dispatched and events just taking an incredibly long time to dispatch
-// without waiting an infinitely long time. Obviously we don't want to do that
-// (block this test from finishing forever), so instead we just listen for
-// events until two animation frames (i.e. requestAnimationFrame callbacks)
-// have happened, then assume that no events will ever be dispatched for the
-// redundant changes if no events were detected in that time.
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-  animation.startTime = startTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> active, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-  animation.startTime = startTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.startTime = startTimeForBeforePhase(animation.timeline);
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.startTime = startTimeForBeforePhase(animation.timeline);
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> active, then back');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  var storedCurrentTime;
-
-  animation.ready.then(t.step_func(function() {
-    storedCurrentTime = animation.currentTime;
-    animation.startTime = null;
-    return animation.ready;
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(t.step_func(function() {
-    assert_equals(animation.currentTime, storedCurrentTime,
-      'Test that hold time is correct');
-    t.done();
-  }));
-}, 'Setting startTime to null');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var savedStartTime = animation.startTime;
-
-    assert_not_equals(animation.startTime, null,
-      'Animation.startTime not null on ready Promise resolve');
-
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-      'Animation.startTime is null after paused');
-    assert_equals(animation.playState, 'paused',
-      'Animation.playState is "paused" after pause() call');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Animation.startTime after pausing');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.cancel();
-    assert_equals(animation.startTime, null,
-                  'The startTime of a cancelled animation should be null');
-    t.done();
-  }));
-}, 'Animation.startTime after cancelling');
-
-    </script>
-  </body>
+</script>
 </html>
--- a/dom/animation/test/css-animations/test_animations-dynamic-changes.html
+++ b/dom/animation/test/css-animations/test_animations-dynamic-changes.html
@@ -1,159 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim1 {
-  to { left: 100px }
-}
-@keyframes anim2 { }
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-
-  var originalAnimation = div.getAnimations()[0];
-  var originalStartTime;
-  var originalCurrentTime;
-
-  // Wait a moment so we can confirm the startTime doesn't change (and doesn't
-  // simply reflect the current time).
-  originalAnimation.ready.then(function() {
-    originalStartTime = originalAnimation.startTime;
-    originalCurrentTime = originalAnimation.currentTime;
-
-    // Wait a moment so we can confirm the startTime doesn't change (and
-    // doesn't simply reflect the current time).
-    return waitForFrame();
-  }).then(t.step_func(function() {
-    div.style.animationDuration = '200s';
-    var animation = div.getAnimations()[0];
-    assert_equals(animation, originalAnimation,
-                  'The same Animation is returned after updating'
-                  + ' animation duration');
-    assert_equals(animation.startTime, originalStartTime,
-                  'Animations returned by getAnimations preserve'
-                  + ' their startTime even when they are updated');
-    // Sanity check
-    assert_not_equals(animation.currentTime, originalCurrentTime,
-                      'Animation.currentTime has updated in next'
-                      + ' requestAnimationFrame callback');
-    t.done();
-  }));
-}, 'Animations preserve their startTime when changed');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, anim1 100s';
-
-  // Store original state
-  var animations = div.getAnimations();
-  var animation1 = animations[0];
-  var animation2 = animations[1];
-
-  // Update first in list
-  div.style.animationDuration = '200s, 100s';
-  animations = div.getAnimations();
-  assert_equals(animations[0], animation1,
-                'First Animation is in same position after update');
-  assert_equals(animations[1], animation2,
-                'Second Animation is in same position after update');
-}, 'Updated Animations maintain their order in the list');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 200s, anim1 100s';
-
-  // Store original state
-  var animations = div.getAnimations();
-  var animation1 = animations[0];
-  var animation2 = animations[1];
-
-  // Wait before continuing so we can compare start times (otherwise the
-  // new Animation objects and existing Animation objects will all have the same
-  // start time).
-  waitForAllAnimations(animations).then(waitForFrame).then(t.step_func(function() {
-    // Swap duration of first and second in list and prepend animation at the
-    // same time
-    div.style.animation = 'anim1 100s, anim1 100s, anim1 200s';
-    animations = div.getAnimations();
-    assert_true(animations[0] !== animation1 && animations[0] !== animation2,
-                'New Animation is prepended to start of list');
-    assert_equals(animations[1], animation1,
-                  'First Animation is in second position after update');
-    assert_equals(animations[2], animation2,
-                  'Second Animation is in third position after update');
-    assert_equals(animations[1].startTime, animations[2].startTime,
-                  'Old Animations have the same start time');
-    // TODO: Check that animations[0].startTime === null
-    return animations[0].ready;
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime > animations[1].startTime,
-                'New Animation has later start time');
-    t.done();
-  }));
-}, 'Only the startTimes of existing animations are preserved');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, anim1 100s';
-  var secondAnimation = div.getAnimations()[1];
-
-  // Wait before continuing so we can compare start times
-  secondAnimation.ready.then(waitForFrame).then(t.step_func(function() {
-    // Trim list of animations
-    div.style.animationName = 'anim1';
-    var animations = div.getAnimations();
-    assert_equals(animations.length, 1, 'List of Animations was trimmed');
-    assert_equals(animations[0], secondAnimation,
-                  'Remaining Animation is the second one in the list');
-    assert_equals(typeof(animations[0].startTime), 'number',
-                  'Remaining Animation has resolved startTime');
-    assert_true(animations[0].startTime < animations[0].timeline.currentTime,
-                'Remaining Animation preserves startTime');
-    t.done();
-  }));
-}, 'Animations are removed from the start of the list while preserving'
-   + ' the state of existing Animations');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-  var firstAddedAnimation = div.getAnimations()[0],
-      secondAddedAnimation,
-      animations;
-
-  // Wait and add second Animation
-  firstAddedAnimation.ready.then(waitForFrame).then(t.step_func(function() {
-    div.style.animation = 'anim1 100s, anim1 100s';
-    secondAddedAnimation = div.getAnimations()[0];
-
-    // Wait again and add another Animation
-    return secondAddedAnimation.ready.then(waitForFrame);
-  })).then(t.step_func(function() {
-    div.style.animation = 'anim1 100s, anim2 100s, anim1 100s';
-    animations = div.getAnimations();
-    assert_not_equals(firstAddedAnimation, secondAddedAnimation,
-                      'New Animations are added to start of the list');
-    assert_equals(animations[0], secondAddedAnimation,
-                  'Second Animation remains in same position after'
-                  + ' interleaving');
-    assert_equals(animations[2], firstAddedAnimation,
-                  'First Animation remains in same position after'
-                  + ' interleaving');
-    return animations[1].ready;
-  })).then(t.step_func(function() {
-    assert_true(animations[1].startTime > animations[0].startTime,
-                'Interleaved animation starts later than existing animations');
-    assert_true(animations[0].startTime > animations[2].startTime,
-                'Original animations retain their start time');
-    t.done();
-  }));
-}, 'Animation state is preserved when interleaving animations in list');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animations-dynamic-changes.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_effect-name.html
+++ b/dom/animation/test/css-animations/test_effect-name.html
@@ -1,37 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes xyz {
-  to { left: 100px }
-}
-</style>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'xyz 100s';
-  assert_equals(div.getAnimations()[0].effect.name, 'xyz',
-                'Animation effect name matches keyframes rule name');
-}, 'Effect name makes keyframe rule');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'x\\yz 100s';
-  assert_equals(div.getAnimations()[0].effect.name, 'xyz',
-                'Escaped animation effect name matches keyframes rule name');
-}, 'Escaped animation name');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'x\\79 z 100s';
-  assert_equals(div.getAnimations()[0].effect.name, 'xyz',
-                'Hex-escaped animation name matches keyframes rule'
-                + ' name');
-}, 'Animation name with hex-escape');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_effect-name.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_effect-target.html
+++ b/dom/animation/test/css-animations/test_effect-target.html
@@ -1,21 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim { }
-</style>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim 100s';
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.effect.target, div,
-    'Animation.target is the animatable div');
-}, 'Returned CSS animations have the correct Animation.target');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_effect-target.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_element-get-animations.html
+++ b/dom/animation/test/css-animations/test_element-get-animations.html
@@ -1,272 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim1 {
-  to { left: 100px }
-}
-@keyframes anim2 {
-  to { top: 100px }
-}
-@keyframes multiPropAnim {
-  to { background: green, opacity: 0.5, left: 100px, top: 100px }
-}
-@keyframes empty { }
-</style>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-  assert_equals(div.getAnimations().length, 0,
-    'getAnimations returns an empty sequence for an element'
-    + ' with no animations');
-}, 'getAnimations for non-animated content');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Add an animation
-  div.style.animation = 'anim1 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns an Animation running CSS Animations');
-  animations[0].ready.then(t.step_func(function() {
-    var startTime = animations[0].startTime;
-    assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
-      'CSS animation has a sensible start time');
-
-    // Wait a moment then add a second animation.
-    //
-    // We wait for the next frame so that we can test that the start times of
-    // the animations differ.
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    div.style.animation = 'anim1 100s, anim2 100s';
-    animations = div.getAnimations();
-    assert_equals(animations.length, 2,
-      'getAnimations returns one Animation for each value of'
-      + ' animation-name');
-    // Wait until both Animations are ready
-    // (We don't make any assumptions about the order of the Animations since
-    //  that is the purpose of the following test.)
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime < animations[1].startTime,
-      'Additional Animations for CSS animations start after the original'
-      + ' animation and appear later in the list');
-    t.done();
-  }));
-}, 'getAnimations for CSS Animations');
-
-test(function(t) {
-  var div = addDiv(t);
-
-  // Add an animation that targets multiple properties
-  div.style.animation = 'multiPropAnim 100s';
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns only one Animation for a CSS Animation'
-    + ' that targets multiple properties');
-}, 'getAnimations for multi-property animations');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Add an animation
-  div.style.backgroundColor = 'red';
-  div.style.animation = 'anim1 100s';
-  window.getComputedStyle(div).backgroundColor;
-
-  // Wait until a frame after the animation starts, then add a transition
-  var animations = div.getAnimations();
-  animations[0].ready.then(waitForFrame).then(t.step_func(function() {
-    div.style.transition = 'all 100s';
-    div.style.backgroundColor = 'green';
-
-    animations = div.getAnimations();
-    assert_equals(animations.length, 2,
-      'getAnimations returns Animations for both animations and'
-      + ' transitions that run simultaneously');
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime > animations[1].startTime,
-      'Animations for transitions appear before animations even if they'
-      + ' start later');
-    t.done();
-  }));
-}, 'getAnimations for both CSS Animations and Transitions at once');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up event listener
-  div.addEventListener('animationend', t.step_func(function() {
-    assert_equals(div.getAnimations().length, 0,
-      'getAnimations does not return Animations for finished '
-      + ' (and non-forwards-filling) CSS Animations');
-    t.done();
-  }));
-
-  // Add a very short animation
-  div.style.animation = 'anim1 0.01s';
-}, 'getAnimations for CSS Animations that have finished');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up event listener
-  div.addEventListener('animationend', t.step_func(function() {
-    assert_equals(div.getAnimations().length, 1,
-      'getAnimations returns Animations for CSS Animations that have'
-      + ' finished but are filling forwards');
-    t.done();
-  }));
-
-  // Add a very short animation
-  div.style.animation = 'anim1 0.01s forwards';
-}, 'getAnimations for CSS Animations that have finished but are'
-   + ' forwards filling');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'none 100s';
-
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 0,
-    'getAnimations returns an empty sequence for an element'
-    + ' with animation-name: none');
-
-  div.style.animation = 'none 100s, anim1 100s';
-  animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns Animations only for those CSS Animations whose'
-    + ' animation-name is not none');
-}, 'getAnimations for CSS Animations with animation-name: none');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'missing 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 0,
-    'getAnimations returns an empty sequence for an element'
-    + ' with animation-name: missing');
-
-  div.style.animation = 'anim1 100s, missing 100s';
-  animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns Animations only for those CSS Animations whose'
-    + ' animation-name is found');
-}, 'getAnimations for CSS Animations with animation-name: missing');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, notyet 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations initally only returns Animations for CSS Animations whose'
-    + ' animation-name is found');
-
-  animations[0].ready.then(waitForFrame).then(t.step_func(function() {
-    var keyframes = '@keyframes notyet { to { left: 100px; } }';
-    document.styleSheets[0].insertRule(keyframes, 0);
-    animations = div.getAnimations();
-    assert_equals(animations.length, 2,
-      'getAnimations includes Animation when @keyframes rule is added'
-      + ' later');
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime < animations[1].startTime,
-      'Newly added animation has a later start time');
-    document.styleSheets[0].deleteRule(0);
-    t.done();
-  }));
-}, 'getAnimations for CSS Animations where the @keyframes rule is added'
-   + ' later');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, anim1 100s';
-  assert_equals(div.getAnimations().length, 2,
-    'getAnimations returns one Animation for each CSS animation-name'
-    + ' even if the names are duplicated');
-}, 'getAnimations for CSS Animations with duplicated animation-name');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'empty 100s';
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns Animations for CSS animations with an'
-    + ' empty keyframes rule');
-}, 'getAnimations for CSS Animations with empty keyframes rule');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns animations for CSS animations whose'
-    + ' delay makes them start later');
-  animations[0].ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(animations[0].startTime <= document.timeline.currentTime,
-      'For CSS Animations in delay phase, the start time of the Animation is'
-      + ' not in the future');
-    t.done();
-  }));
-}, 'getAnimations for CSS animations in delay phase');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 0s 100s';
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns animations for CSS animations whose'
-    + ' duration is zero');
-  div.remove();
-}, 'getAnimations for zero-duration CSS Animations');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-  var originalAnimation = div.getAnimations()[0];
-
-  // Update pause state (an Animation change)
-  div.style.animationPlayState = 'paused';
-  var pendingAnimation = div.getAnimations()[0];
-  assert_equals(pendingAnimation.playState, 'pending',
-                'animation\'s play state is updated');
-  assert_equals(originalAnimation, pendingAnimation,
-                'getAnimations returns the same objects even when their'
-                + ' play state changes');
-
-  // Update duration (an Animation change)
-  div.style.animationDuration = '200s';
-  var extendedAnimation = div.getAnimations()[0];
-  // FIXME: Check extendedAnimation.effect.timing.duration has changed once the
-  // API is available
-  assert_equals(originalAnimation, extendedAnimation,
-                'getAnimations returns the same objects even when their'
-                + ' duration changes');
-}, 'getAnimations returns objects with the same identity');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns an animation before cancelling');
-
-  var animation = div.getAnimations()[0];
-
-  animation.cancel();
-  assert_equals(div.getAnimations().length, 0,
-    'getAnimations does not return cancelled animations');
-
-  animation.play();
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns cancelled animations that have been re-started');
-
-}, 'getAnimations for CSS Animations that are cancelled');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_element-get-animations.html");
+  });
 </script>
+</html>
copy from dom/animation/test/css-transitions/test_animation-cancel.html
copy to dom/animation/test/css-transitions/file_animation-cancel.html
--- a/dom/animation/test/css-transitions/test_animation-cancel.html
+++ b/dom/animation/test/css-transitions/file_animation-cancel.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t, { style: 'margin-left: 0px' });
   flushComputedStyle(div);
 
   div.style.transition = 'margin-left 100s';
@@ -120,10 +118,11 @@ test(function(t) {
   assert_equals(getComputedStyle(div).marginLeft, '1000px',
                 'margin-left style is still not animated after updating'
                 + ' transition-duration');
   assert_equals(animation.playState, 'idle',
                 'Transition is still idle after updating transition-duration');
 }, 'After cancelling a transition, updating transition properties doesn\'t make'
    + ' it live again');
 
+done();
 </script>
-</html>
+</body>
copy from dom/animation/test/css-transitions/test_animation-currenttime.html
copy to dom/animation/test/css-transitions/file_animation-currenttime.html
--- a/dom/animation/test/css-transitions/test_animation-currenttime.html
+++ b/dom/animation/test/css-transitions/file_animation-currenttime.html
@@ -7,22 +7,19 @@
     <style>
 
 .animated-div {
   margin-left: 100px;
   transition: margin-left 1000s linear 1000s;
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -305,11 +302,12 @@ async_test(function(t) {
       'Animation.currentTime is unchanged after pausing');
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Animation.currentTime after pausing');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-transitions/test_animation-finished.html
copy to dom/animation/test/css-transitions/file_animation-finished.html
--- a/dom/animation/test/css-transitions/test_animation-finished.html
+++ b/dom/animation/test/css-transitions/file_animation-finished.html
@@ -1,22 +1,20 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 
 .animated-div {
   margin-left: 100px;
   transition: margin-left 1000s linear 1000s;
 }
 
 </style>
+<body>
 <script>
 
 'use strict';
 
 const ANIM_DELAY_MS = 1000000; // 1000s
 const ANIM_DUR_MS = 1000000; // 1000s
 
 async_test(function(t) {
@@ -53,9 +51,11 @@ async_test(function(t) {
     // 1108055) we should use that here.
     assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
                   'Replaying a finished reversed transition should reset ' +
                   'its currentTime to the end of the effect');
     t.done();
   }));
 }, 'Test restarting a reversed finished transition');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_animation-pausing.html
copy to dom/animation/test/css-transitions/file_animation-pausing.html
--- a/dom/animation/test/css-transitions/test_animation-pausing.html
+++ b/dom/animation/test/css-transitions/file_animation-pausing.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 function getMarginLeft(cs) {
   return parseFloat(cs.marginLeft);
 }
 
 async_test(function(t) {
@@ -42,9 +40,11 @@ async_test(function(t) {
     return animation.ready.then(waitForFrame);
   })).then(t.step_func(function() {
     assert_true(getMarginLeft(cs) > previousAnimVal,
                 'margin-left increases after calling play()');
     t.done();
   }));
 }, 'pause() and play() a transition');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_animation-ready.html
copy to dom/animation/test/css-transitions/file_animation-ready.html
--- a/dom/animation/test/css-transitions/test_animation-ready.html
+++ b/dom/animation/test/css-transitions/file_animation-ready.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.transform = 'translate(0px)';
   window.getComputedStyle(div).transform;
   div.style.transition = 'transform 100s';
@@ -88,9 +86,11 @@ async_test(function(t) {
 
   // Now update the transition to animate to something not-interpolable
   div.style.marginLeft = 'auto';
   window.getComputedStyle(div).marginLeft;
 
 }, 'ready promise is rejected when a transition is cancelled by changing'
    + ' the transition property to something not interpolable');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_animation-starttime.html
copy to dom/animation/test/css-transitions/file_animation-starttime.html
--- a/dom/animation/test/css-transitions/test_animation-starttime.html
+++ b/dom/animation/test/css-transitions/file_animation-starttime.html
@@ -7,22 +7,19 @@
     <style>
 
 .animated-div {
   margin-left: 100px;
   transition: margin-left 1000s linear 1000s;
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -282,11 +279,12 @@ async_test(function(t) {
       'Animation.playState is "paused" after pause() call');
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Animation.startTime after paused');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-transitions/test_effect-name.html
copy to dom/animation/test/css-transitions/file_effect-name.html
--- a/dom/animation/test/css-transitions/test_effect-name.html
+++ b/dom/animation/test/css-transitions/file_effect-name.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
 
   // Add a transition
   div.style.left = '0px';
@@ -16,9 +14,11 @@ test(function(t) {
   div.style.transition = 'all 100s';
   div.style.left = '100px';
 
   assert_equals(div.getAnimations()[0].effect.name, 'left',
                 'The name for the transitions corresponds to the property ' +
                 'being transitioned');
 }, 'Effect name for transitions');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_effect-target.html
copy to dom/animation/test/css-transitions/file_effect-target.html
--- a/dom/animation/test/css-transitions/test_effect-target.html
+++ b/dom/animation/test/css-transitions/file_effect-target.html
@@ -1,23 +1,23 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.left = '0px';
   window.getComputedStyle(div).transitionProperty;
   div.style.transition = 'left 100s';
   div.style.left = '100px';
 
   var animation = div.getAnimations()[0];
   assert_equals(animation.effect.target, div,
     'Animation.target is the animatable div');
 }, 'Returned CSS transitions have the correct Animation.target');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_element-get-animations.html
copy to dom/animation/test/css-transitions/file_element-get-animations.html
--- a/dom/animation/test/css-transitions/test_element-get-animations.html
+++ b/dom/animation/test/css-transitions/file_element-get-animations.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
 
   // Add a couple of transitions
   div.style.left = '0px';
@@ -86,9 +84,11 @@ test(function(t) {
   div.style.transition = 'all 100s';
   div.style.setProperty('-vendor-unsupported', '100px', '');
 
   assert_equals(div.getAnimations().length, 0,
     'getAnimations returns an empty sequence for a transition'
     + ' of an unsupported property');
 }, 'getAnimations for transition on unsupported property');
 
+done();
 </script>
+</body>
--- a/dom/animation/test/css-transitions/test_animation-cancel.html
+++ b/dom/animation/test/css-transitions/test_animation-cancel.html
@@ -1,129 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_not_equals(getComputedStyle(div).marginLeft, '1000px',
-                      'transform style is animated before cancelling');
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, div.style.marginLeft,
-                  'transform style is no longer animated after cancelling');
-    t.done();
-  }));
-}, 'Animated style is cleared after cancelling a running CSS transition');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  div.addEventListener('transitionend', t.step_func(function() {
-    assert_unreached('Got unexpected end event on cancelled transition');
-  }));
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    // Seek to just before the end then cancel
-    animation.currentTime = 99.9 * 1000;
-    animation.cancel();
-
-    // Then wait a couple of frames and check that no event was dispatched
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Cancelled CSS transitions do not dispatch events');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                  'margin-left style is not animated after cancelling');
-    animation.play();
-    assert_equals(getComputedStyle(div).marginLeft, '0px',
-                  'margin-left style is animated after re-starting transition');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Transition succeeds in running after being re-started');
-    t.done();
-  }));
-}, 'After cancelling a transition, it can still be re-used');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.finish();
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                  'margin-left style is not animated after cancelling');
-    animation.play();
-    assert_equals(getComputedStyle(div).marginLeft, '0px',
-                  'margin-left style is animated after re-starting transition');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Transition succeeds in running after being re-started');
-    t.done();
-  }));
-}, 'After cancelling a finished transition, it can still be re-used');
-
-test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                'margin-left style is not animated after cancelling');
-
-  // Trigger a change to a transition property and check that this
-  // doesn't cause the animation to become live again
-  div.style.transitionDuration = '200s';
-  flushComputedStyle(div);
-  assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                'margin-left style is still not animated after updating'
-                + ' transition-duration');
-  assert_equals(animation.playState, 'idle',
-                'Transition is still idle after updating transition-duration');
-}, 'After cancelling a transition, updating transition properties doesn\'t make'
-   + ' it live again');
-
+setup({explicit_done: true});