merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 09 Feb 2016 12:00:16 +0100
changeset 283476 d1a54ae63da7ebc4bc1eeb5b613e8ec29bfcb80a
parent 283434 a6b8d2c921e3f80500c32976ecedf88631433e0a (current diff)
parent 283475 49945e5fe79e6cf2b083f56fd49d917ca886db0b (diff)
child 283561 2dfb45d74f42d2a0010696f5fd47c7a7da94cedb
push id29985
push usercbook@mozilla.com
push dateTue, 09 Feb 2016 11:00:25 +0000
treeherdermozilla-central@d1a54ae63da7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
d1a54ae63da7 / 47.0a1 / 20160209030347 / files
nightly linux64
d1a54ae63da7 / 47.0a1 / 20160209030347 / files
nightly mac
d1a54ae63da7 / 47.0a1 / 20160209030347 / files
nightly win32
d1a54ae63da7 / 47.0a1 / 20160209030347 / files
nightly win64
d1a54ae63da7 / 47.0a1 / 20160209030347 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
devtools/server/tests/browser/browser_animation_name.js
mobile/android/base/resources/drawable/find_matchcase_selector.xml
--- a/.eslintignore
+++ b/.eslintignore
@@ -87,17 +87,16 @@ devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/eyedropper/**
 devtools/client/framework/**
 # devtools/client/inspector/shared/*.js files are eslint-clean, so they aren't
 # included in the ignore list.
 devtools/client/inspector/computed/**
 devtools/client/inspector/fonts/**
-devtools/client/inspector/layout/**
 devtools/client/inspector/markup/test/**
 devtools/client/inspector/rules/**
 devtools/client/inspector/shared/test/**
 devtools/client/inspector/test/**
 devtools/client/inspector/*.js
 devtools/client/jsonview/**
 devtools/client/memory/**
 devtools/client/netmonitor/**
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -421,19 +421,22 @@ var gGestureSupport = {
    */
   _getPref: function GS__getPref(aPref, aDef) {
     // Preferences branch under which all gestures preferences are stored
     const branch = "browser.gesture.";
 
     try {
       // Determine what type of data to load based on default value's type
       let type = typeof aDef;
-      let getFunc = "get" + (type == "boolean" ? "Bool" :
-                             type == "number" ? "Int" : "Char") + "Pref";
-      return gPrefService[getFunc](branch + aPref);
+      let getFunc = "Char";
+      if (type == "boolean")
+        getFunc = "Bool";
+      else if (type == "number")
+        getFunc = "Int";
+      return gPrefService["get" + getFunc + "Pref"](branch + aPref);
     }
     catch (e) {
       return aDef;
     }
   },
 
   /**
    * Perform rotation for ImageDocuments
new file mode 100644
--- /dev/null
+++ b/browser/base/content/browser-refreshblocker.js
@@ -0,0 +1,84 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * If the user has opted into blocking refresh and redirect attempts by
+ * default, this handles showing the notification to the user which
+ * gives them the option to let the refresh or redirect proceed.
+ */
+var RefreshBlocker = {
+  init() {
+    gBrowser.addEventListener("RefreshBlocked", this);
+  },
+
+  uninit() {
+    gBrowser.removeEventListener("RefreshBlocked", this);
+  },
+
+  handleEvent: function(event) {
+    if (event.type == "RefreshBlocked") {
+      this.block(event.originalTarget, event.detail);
+    }
+  },
+
+  /**
+   * Shows the blocked refresh / redirect notification for some browser.
+   *
+   * @param browser (<xul:browser>)
+   *        The browser that had the refresh blocked. This will be the browser
+   *        for which we'll show the notification on.
+   * @param data (object)
+   *        An object with the following properties:
+   *
+   *        URI (string)
+   *          The URI that a page is attempting to refresh or redirect to.
+   *
+   *        delay (int)
+   *          The delay (in milliseconds) before the page was going to reload
+   *          or redirect.
+   *
+   *        sameURI (bool)
+   *          true if we're refreshing the page. false if we're redirecting.
+   *
+   *        outerWindowID (int)
+   *          The outerWindowID of the frame that requested the refresh or
+   *          redirect.
+   */
+  block(browser, data) {
+    let brandBundle = document.getElementById("bundle_brand");
+    let brandShortName = brandBundle.getString("brandShortName");
+    let message =
+      gNavigatorBundle.getFormattedString(data.sameURI ? "refreshBlocked.refreshLabel"
+                                                       : "refreshBlocked.redirectLabel",
+                                          [brandShortName]);
+
+    let notificationBox = gBrowser.getNotificationBox(browser);
+    let notification = notificationBox.getNotificationWithValue("refresh-blocked");
+
+    if (notification) {
+      notification.label = message;
+    } else {
+      let refreshButtonText =
+        gNavigatorBundle.getString("refreshBlocked.goButton");
+      let refreshButtonAccesskey =
+        gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
+
+      let buttons = [{
+        label: refreshButtonText,
+        accessKey: refreshButtonAccesskey,
+        callback: function (notification, button) {
+          if (browser.messageManager) {
+            browser.messageManager.sendAsyncMessage("RefreshBlocker:Refresh", data);
+          }
+        }
+      }];
+
+      notificationBox.appendNotification(message, "refresh-blocked",
+                                         "chrome://browser/skin/Info.png",
+                                         notificationBox.PRIORITY_INFO_MEDIUM,
+                                         buttons);
+    }
+  }
+};
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -918,16 +918,17 @@ var gBrowserInit = {
     DOMLinkHandler.init();
     gPageStyleMenu.init();
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     FeedHandler.init();
     DevEdition.init();
     AboutPrivateBrowsingListener.init();
     TrackingProtection.init();
+    RefreshBlocker.init();
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
     mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
 
     window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
@@ -1441,16 +1442,18 @@ var gBrowserInit = {
     BrowserOnClick.uninit();
 
     FeedHandler.uninit();
 
     DevEdition.uninit();
 
     TrackingProtection.uninit();
 
+    RefreshBlocker.uninit();
+
     gMenuButtonUpdateBadge.uninit();
 
     gMenuButtonBadgeManager.uninit();
 
     SidebarUI.uninit();
 
     // Now either cancel delayedStartup, or clean up the services initialized from
     // it.
@@ -4691,64 +4694,16 @@ var TabsProgressListener = {
     // longer exists)
     if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get)
       PopupNotifications.locationChange(aBrowser);
 
     gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
 
     FullZoom.onLocationChange(aLocationURI, false, aBrowser);
   },
-
-  onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {
-    if (gPrefService.getBoolPref("accessibility.blockautorefresh")) {
-      let brandBundle = document.getElementById("bundle_brand");
-      let brandShortName = brandBundle.getString("brandShortName");
-      let refreshButtonText =
-        gNavigatorBundle.getString("refreshBlocked.goButton");
-      let refreshButtonAccesskey =
-        gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
-      let message =
-        gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel"
-                                                     : "refreshBlocked.redirectLabel",
-                                            [brandShortName]);
-      let docShell = aWebProgress.DOMWindow
-                                 .QueryInterface(Ci.nsIInterfaceRequestor)
-                                 .getInterface(Ci.nsIWebNavigation)
-                                 .QueryInterface(Ci.nsIDocShell);
-      let notificationBox = gBrowser.getNotificationBox(aBrowser);
-      let notification = notificationBox.getNotificationWithValue("refresh-blocked");
-      if (notification) {
-        notification.label = message;
-        notification.refreshURI = aURI;
-        notification.delay = aDelay;
-        notification.docShell = docShell;
-      } else {
-        let buttons = [{
-          label: refreshButtonText,
-          accessKey: refreshButtonAccesskey,
-          callback: function (aNotification, aButton) {
-            var refreshURI = aNotification.docShell
-                                          .QueryInterface(Ci.nsIRefreshURI);
-            refreshURI.forceRefreshURI(aNotification.refreshURI,
-                                       aNotification.delay, true);
-          }
-        }];
-        notification =
-          notificationBox.appendNotification(message, "refresh-blocked",
-                                             "chrome://browser/skin/Info.png",
-                                             notificationBox.PRIORITY_INFO_MEDIUM,
-                                             buttons);
-        notification.refreshURI = aURI;
-        notification.delay = aDelay;
-        notification.docShell = docShell;
-      }
-      return false;
-    }
-    return true;
-  }
 }
 
 function nsBrowserAccess() { }
 
 nsBrowserAccess.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
 
   _openURIInNewTab: function(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -272,19 +272,23 @@ ContentSearchUIController.prototype = {
         altKey: aEvent.altKey,
         button: aEvent.button,
       },
     };
 
     if (this.suggestionAtIndex(this.selectedIndex)) {
       eventData.selection = {
         index: this.selectedIndex,
-        kind: aEvent instanceof MouseEvent ? "mouse" :
-              aEvent instanceof KeyboardEvent ? "key" : undefined,
+        kind: undefined,
       };
+      if (aEvent instanceof MouseEvent) {
+        eventData.selection.kind = "mouse";
+      } else if (aEvent instanceof KeyboardEvent) {
+        eventData.selection.kind = "key";
+      }
     }
 
     this._sendMsg("Search", eventData);
     this.addInputValueToFormHistory();
   },
 
   _onInput: function () {
     if (!this.input.value) {
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -19,16 +19,17 @@
 <script type="application/javascript" src="chrome://browser/content/browser-devedition.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-eme.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-feeds.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullScreen.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-fullZoom.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-gestureSupport.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-plugins.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-refreshblocker.js"/>
 #ifdef MOZ_SAFE_BROWSING
 <script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
 #endif
 <script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -295,17 +295,17 @@ function initPluginsRow() {
         permissionMap.set(permString, name);
       }
     }
   }
 
   let entries = Array.from(permissionMap, item => ({ name: item[1], permission: item[0] }));
 
   entries.sort(function(a, b) {
-    return a.name < b.name ? -1 : (a.name == b.name ? 0 : 1);
+    return a.name.localeCompare(b.name);
   });
 
   let permissionEntries = entries.map(p => fillInPluginPermissionTemplate(p.name, p.permission));
 
   let permPluginsRow = document.getElementById("perm-plugins-row");
   clearPluginPermissionTemplate();
   if (permissionEntries.length < 1) {
     permPluginsRow.hidden = true;
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -683,12 +683,81 @@ var DOMFullscreenHandler = {
         sendAsyncMessage("DOMFullscreen:Painted");
         break;
       }
     }
   }
 };
 DOMFullscreenHandler.init();
 
+var RefreshBlocker = {
+  init() {
+    this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+                     .createInstance(Ci.nsIWebProgress);
+    this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_REFRESH);
+
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebProgress);
+    webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_REFRESH);
+
+    addMessageListener("RefreshBlocker:Refresh", this);
+  },
+
+  uninit() {
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebProgress);
+    webProgress.removeProgressListener(this._filter);
+
+    this._filter.removeProgressListener(this);
+    this._filter = null;
+
+    removeMessageListener("RefreshBlocker:Refresh", this);
+  },
+
+  onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
+    if (Services.prefs.getBoolPref("accessibility.blockautorefresh")) {
+      let win = aWebProgress.DOMWindow;
+      let outerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils)
+                             .outerWindowID;
+
+      sendAsyncMessage("RefreshBlocker:Blocked", {
+        URI: aURI.spec,
+        originCharset: aURI.originCharset,
+        delay: aDelay,
+        sameURI: aSameURI,
+        outerWindowID,
+      });
+
+      return false;
+    }
+
+    return true;
+  },
+
+  receiveMessage(message) {
+    let data = message.data;
+
+    if (message.name == "RefreshBlocker:Refresh") {
+      let win = Services.wm.getOuterWindowWithId(data.outerWindowID);
+      let refreshURI = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell)
+                          .QueryInterface(Ci.nsIRefreshURI);
+
+      let URI = BrowserUtils.makeURI(data.URI, data.originCharset, null);
+
+      refreshURI.forceRefreshURI(URI, data.delay, true);
+    }
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener2,
+                                         Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsISupports]),
+};
+
+RefreshBlocker.init();
+
 ExtensionContent.init(this);
 addEventListener("unload", () => {
   ExtensionContent.uninit(this);
+  RefreshBlocker.uninit();
 });
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4025,17 +4025,17 @@
               break;
           }
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
         <body><![CDATA[
-          let json = aMessage.json;
+          let data = aMessage.data;
           let browser = aMessage.target;
 
           switch (aMessage.name) {
             case "DOMTitleChanged": {
               let tab = this.getTabForBrowser(browser);
               if (!tab || tab.hasAttribute("pending"))
                 return undefined;
               let titleChanged = this.setTabTitle(tab);
@@ -4057,42 +4057,42 @@
               if (tab) {
                 // Skip running PermitUnload since it already happened in
                 // the content process.
                 this.removeTab(tab, {skipPermitUnload: true});
               }
               break;
             }
             case "contextmenu": {
-              let spellInfo = aMessage.data.spellInfo;
+              let spellInfo = data.spellInfo;
               if (spellInfo)
                 spellInfo.target = aMessage.target.messageManager;
-              let documentURIObject = makeURI(aMessage.data.docLocation,
-                                              aMessage.data.charSet,
-                                              makeURI(aMessage.data.baseURI));
+              let documentURIObject = makeURI(data.docLocation,
+                                              data.charSet,
+                                              makeURI(data.baseURI));
               gContextMenuContentData = { isRemote: true,
                                           event: aMessage.objects.event,
                                           popupNode: aMessage.objects.popupNode,
                                           browser: browser,
-                                          editFlags: aMessage.data.editFlags,
+                                          editFlags: data.editFlags,
                                           spellInfo: spellInfo,
-                                          principal: aMessage.data.principal,
-                                          customMenuItems: aMessage.data.customMenuItems,
-                                          addonInfo: aMessage.data.addonInfo,
+                                          principal: data.principal,
+                                          customMenuItems: data.customMenuItems,
+                                          addonInfo: data.addonInfo,
                                           documentURIObject: documentURIObject,
-                                          docLocation: aMessage.data.docLocation,
-                                          charSet: aMessage.data.charSet,
-                                          referrer: aMessage.data.referrer,
-                                          referrerPolicy: aMessage.data.referrerPolicy,
-                                          contentType: aMessage.data.contentType,
-                                          contentDisposition: aMessage.data.contentDisposition,
-                                          frameOuterWindowID: aMessage.data.frameOuterWindowID,
-                                          selectionInfo: aMessage.data.selectionInfo,
-                                          disableSetDesktopBackground: aMessage.data.disableSetDesktopBg,
-                                          loginFillInfo: aMessage.data.loginFillInfo,
+                                          docLocation: data.docLocation,
+                                          charSet: data.charSet,
+                                          referrer: data.referrer,
+                                          referrerPolicy: data.referrerPolicy,
+                                          contentType: data.contentType,
+                                          contentDisposition: data.contentDisposition,
+                                          frameOuterWindowID: data.frameOuterWindowID,
+                                          selectionInfo: data.selectionInfo,
+                                          disableSetDesktopBackground: data.disableSetDesktopBg,
+                                          loginFillInfo: data.loginFillInfo,
                                         };
               let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
               let event = gContextMenuContentData.event;
               popup.openPopupAtScreen(event.screenX, event.screenY, true);
               break;
             }
             case "DOMServiceWorkerFocusClient":
             case "DOMWebNotificationClicked": {
@@ -4113,33 +4113,44 @@
               break;
             }
             case "Findbar:Keypress": {
               let tab = this.getTabForBrowser(browser);
               // If the find bar for this tab is not yet alive, only initialize
               // it if there's a possibility FindAsYouType will be used.
               // There's no point in doing it for most random keypresses.
               if (!this.isFindBarInitialized(tab) &&
-                aMessage.data.shouldFastFind) {
+                data.shouldFastFind) {
                 let shouldFastFind = this._findAsYouType;
                 if (!shouldFastFind) {
                   // Please keep in sync with toolkit/content/widgets/findbar.xml
                   const FAYT_LINKS_KEY = "'";
                   const FAYT_TEXT_KEY = "/";
-                  let charCode = aMessage.data.fakeEvent.charCode;
+                  let charCode = data.fakeEvent.charCode;
                   let key = charCode ? String.fromCharCode(charCode) : null;
                   shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY;
                 }
                 if (shouldFastFind) {
                   // Make sure we return the result.
                   return this.getFindBar(tab).receiveMessage(aMessage);
                 }
               }
               break;
             }
+            case "RefreshBlocker:Blocked": {
+              let event = new CustomEvent("RefreshBlocked", {
+                bubbles: true,
+                cancelable: false,
+                detail: data,
+              });
+
+              browser.dispatchEvent(event);
+
+              break;
+            }
 
           }
         ]]></body>
       </method>
 
       <method name="observe">
         <parameter name="aSubject"/>
         <parameter name="aTopic"/>
@@ -4239,16 +4250,17 @@
             // which does asynchronous tab switching.
             this.mPanelContainer.classList.add("tabbrowser-tabpanels");
           } else {
             this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID,
                                               this.mCurrentBrowser);
           }
           messageManager.addMessageListener("DOMWebNotificationClicked", this);
           messageManager.addMessageListener("DOMServiceWorkerFocusClient", this);
+          messageManager.addMessageListener("RefreshBlocker:Blocked", this);
 
           // To correctly handle keypresses for potential FindAsYouType, while
           // the tab's find bar is not yet initialized.
           this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
           Services.prefs.addObserver("accessibility.typeaheadfind", this, false);
           messageManager.addMessageListener("Findbar:Keypress", this);
         ]]>
       </constructor>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -363,16 +363,17 @@ skip-if = (os == 'linux') || (e10s && de
 [browser_printpreview.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1101973 - breaks the next test in e10s, and may be responsible for later timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
 [browser_private_browsing_window.js]
 skip-if = buildapp == 'mulet'
 [browser_private_no_prompt.js]
 skip-if = buildapp == 'mulet'
 [browser_purgehistory_clears_sh.js]
 [browser_PageMetaData_pushstate.js]
+[browser_refreshBlocker.js]
 [browser_relatedTabs.js]
 [browser_remoteTroubleshoot.js]
 support-files =
   test_remoteTroubleshoot.html
 [browser_remoteWebNavigation_postdata.js]
 [browser_removeTabsToTheEnd.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 [browser_restore_isAppTab.js]
--- a/browser/base/content/test/general/browser_aboutTabCrashed.js
+++ b/browser/base/content/test/general/browser_aboutTabCrashed.js
@@ -120,73 +120,73 @@ function crashTabTestHelper(fieldValues,
 
 /**
  * Tests what we send with the crash report by default. By default, we do not
  * send any comments, the URL of the crashing page, or the email address of
  * the user.
  */
 add_task(function* test_default() {
   yield crashTabTestHelper({}, {
-    "Comments": "",
+    "Comments": null,
     "URL": "",
-    "Email": "",
+    "Email": null,
   });
 });
 
 /**
  * Test just sending a comment.
  */
 add_task(function* test_just_a_comment() {
   yield crashTabTestHelper({
     comments: COMMENTS,
   }, {
     "Comments": COMMENTS,
     "URL": "",
-    "Email": "",
+    "Email": null,
   });
 });
 
 /**
  * Test that we don't send email if emailMe is unchecked
  */
 add_task(function* test_no_email() {
   yield crashTabTestHelper({
     email: EMAIL,
     emailMe: false,
   }, {
-    "Comments": "",
+    "Comments": null,
     "URL": "",
-    "Email": "",
+    "Email": null,
   });
 });
 
 /**
  * Test that we can send an email address if emailMe is checked
  */
 add_task(function* test_yes_email() {
   yield crashTabTestHelper({
     email: EMAIL,
     emailMe: true,
   }, {
-    "Comments": "",
+    "Comments": null,
     "URL": "",
     "Email": EMAIL,
   });
 });
 
 /**
  * Test that we will send the URL of the page if includeURL is checked.
  */
 add_task(function* test_send_URL() {
   yield crashTabTestHelper({
     includeURL: true,
   }, {
-    "Comments": "",
+    "Comments": null,
     "URL": PAGE,
-    "Email": "",
+    "Email": null,
   });
 });
 
 /**
  * Test that we can send comments, the email address, and the URL
  */
 add_task(function* test_send_all() {
   yield crashTabTestHelper({
@@ -195,8 +195,9 @@ add_task(function* test_send_all() {
     email: EMAIL,
     comments: COMMENTS,
   }, {
     "Comments": COMMENTS,
     "URL": PAGE,
     "Email": EMAIL,
   });
 });
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_refreshBlocker.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+const PREF = "accessibility.blockautorefresh";
+
+/**
+ * Goes into the content, and simulates a meta-refresh header at a very
+ * low level, and checks to see if it was blocked. This will always cancel
+ * the refresh, regardless of whether or not the refresh was blocked.
+ *
+ * @param browser (<xul:browser>)
+ *        The browser to test for refreshing.
+ * @param expectRefresh (bool)
+ *        Whether or not we expect the refresh attempt to succeed.
+ * @returns Promise
+ */
+function* attemptRefresh(browser, expectRefresh) {
+  yield ContentTask.spawn(browser, expectRefresh, function*(expectRefresh) {
+    let URI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
+    let refresher = docShell.QueryInterface(Ci.nsIRefreshURI);
+    refresher.refreshURI(URI, 0, false, true);
+
+    is(refresher.refreshPending, expectRefresh,
+       "Got the right refreshPending state");
+
+    if (refresher.refreshPending) {
+      // Cancel the pending refresh
+      refresher.cancelRefreshURITimers();
+    }
+  });
+}
+
+/**
+ * Tests that we can enable the blocking pref and block a refresh
+ * from occurring while showing a notification bar. Also tests that
+ * when we disable the pref, that refreshes can go through again.
+ */
+add_task(function* test_can_enable_and_block() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE,
+  }, function*(browser) {
+    // By default, we should be able to reload the page.
+    yield attemptRefresh(browser, true);
+
+    yield pushPrefs(["accessibility.blockautorefresh", true]);
+
+    let notificationPromise =
+      BrowserTestUtils.waitForNotificationBar(gBrowser, browser,
+                                              "refresh-blocked");
+
+    yield attemptRefresh(browser, false);
+
+    yield notificationPromise;
+
+    yield pushPrefs(["accessibility.blockautorefresh", false]);
+
+    // Page reloads should go through again.
+    yield attemptRefresh(browser, true);
+  });
+});
+
+/**
+ * Tests that when a refresh is blocked that we show a notification
+ * bar, and that when we click on the "Allow" button, the refresh
+ * can then go through.
+ */
+add_task(function* test_can_allow_refresh() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE,
+  }, function*(browser) {
+    yield pushPrefs(["accessibility.blockautorefresh", true]);
+
+    let notificationPromise =
+      BrowserTestUtils.waitForNotificationBar(gBrowser, browser,
+                                              "refresh-blocked");
+
+    yield attemptRefresh(browser, false);
+    let notification = yield notificationPromise;
+
+    // Then click the button to submit the crash report.
+    let buttons = notification.querySelectorAll(".notification-button");
+    is(buttons.length, 1, "Should have one button.");
+
+    // Prepare a Promise that should resolve when the refresh goes through
+    let refreshPromise = BrowserTestUtils.browserLoaded(browser);
+    buttons[0].click();
+
+    yield refreshPromise;
+  });
+});
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -1144,29 +1144,34 @@ function getPropertyBagValue(bag, key) {
   return null;
 }
 
 /**
  * Returns a Promise that resolves once a crash report has
  * been submitted. This function will also test the crash
  * reports extra data to see if it matches expectedExtra.
  *
- * @param expectedExtra
+ * @param expectedExtra (object)
  *        An Object whose key-value pairs will be compared
  *        against the key-value pairs in the extra data of the
  *        crash report. A test failure will occur if there is
  *        a mismatch.
  *
- *        Note that this will only check the values that exist
+ *        If the value of the key-value pair is "null", this will
+ *        be interpreted as "this key should not be included in the
+ *        extra data", and will cause a test failure if it is detected
+ *        in the crash report.
+ *
+ *        Note that this will ignore any keys that are not included
  *        in expectedExtra. It's possible that the crash report
  *        will contain other extra information that is not
  *        compared against.
  * @returns Promise
  */
-function promiseCrashReport(expectedExtra) {
+function promiseCrashReport(expectedExtra={}) {
   return Task.spawn(function*() {
     info("Starting wait on crash-report-status");
     let [subject, data] =
       yield TestUtils.topicObserved("crash-report-status", (subject, data) => {
         return data == "success";
       });
     info("Topic observed!");
 
@@ -1195,14 +1200,18 @@ function promiseCrashReport(expectedExtr
     }
 
     info("Iterating crash report extra keys");
     let enumerator = extra.enumerator;
     while (enumerator.hasMoreElements()) {
       let key = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
       let value = extra.getPropertyAsAString(key);
       if (key in expectedExtra) {
-        is(value, expectedExtra[key],
-           `Crash report had the right extra value for ${key}`);
+        if (expectedExtra[key] == null) {
+          ok(false, `Got unexpected key ${key} with value ${value}`);
+        } else {
+          is(value, expectedExtra[key],
+             `Crash report had the right extra value for ${key}`);
+        }
       }
     }
   });
 }
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -84,16 +84,17 @@ browser.jar:
         content/browser/browser-eme.js                (content/browser-eme.js)
         content/browser/browser-feeds.js              (content/browser-feeds.js)
         content/browser/browser-fullScreen.js         (content/browser-fullScreen.js)
         content/browser/browser-fullZoom.js           (content/browser-fullZoom.js)
         content/browser/browser-fxaccounts.js         (content/browser-fxaccounts.js)
         content/browser/browser-gestureSupport.js     (content/browser-gestureSupport.js)
         content/browser/browser-places.js             (content/browser-places.js)
         content/browser/browser-plugins.js            (content/browser-plugins.js)
+        content/browser/browser-refreshblocker.js     (content/browser-refreshblocker.js)
 #ifdef MOZ_SAFE_BROWSING
         content/browser/browser-safebrowsing.js       (content/browser-safebrowsing.js)
 #endif
         content/browser/browser-sidebar.js            (content/browser-sidebar.js)
         content/browser/browser-social.js             (content/browser-social.js)
         content/browser/browser-syncui.js             (content/browser-syncui.js)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 #ifdef CAN_DRAW_IN_TITLEBAR
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -911,18 +911,22 @@ CustomizeMode.prototype = {
       } else if (wrapper.hasAttribute("haswideitem")) {
         wrapper.removeAttribute("haswideitem");
       }
     }
 
     let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
     wrapper.setAttribute("removable", removable);
 
-    let contextMenuAttrName = aNode.getAttribute("context") ? "context" :
-                                aNode.getAttribute("contextmenu") ? "contextmenu" : "";
+    let contextMenuAttrName = "";
+    if (aNode.getAttribute("context")) {
+      contextMenuAttrName = "context";
+    } else if (aNode.getAttribute("contextmenu")) {
+      contextMenuAttrName = "contextmenu";
+    }
     let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
     let contextMenuForPlace = aPlace == "panel" ?
                                 kPanelItemContextMenu :
                                 kPaletteItemContextMenu;
     if (aPlace != "toolbar") {
       wrapper.setAttribute("context", contextMenuForPlace);
     }
     // Only keep track of the menu if it is non-default.
--- a/browser/components/migration/ESEDBReader.jsm
+++ b/browser/components/migration/ESEDBReader.jsm
@@ -56,16 +56,17 @@ var COLUMN_TYPES = {
   JET_coltypDateTime:      8, /* Integral date, fractional time */
   JET_coltypBinary:        9, /* Binary data, < 255 bytes */
   JET_coltypText:         10, /* ANSI text, case insensitive, < 255 bytes */
   JET_coltypLongBinary:   11, /* Binary data, long value */
   JET_coltypLongText:     12, /* ANSI text, long value */
 
   JET_coltypUnsignedLong: 14, /* 4-byte unsigned integer */
   JET_coltypLongLong:     15, /* 8-byte signed integer */
+  JET_coltypGUID:         16, /* 16-byte globally unique identifier */
 };
 
 // Not very efficient, but only used for error messages
 function getColTypeName(numericValue) {
   return Object.keys(COLUMN_TYPES).find(t => COLUMN_TYPES[t] == numericValue) || "unknown";
 }
 
 // All type constants and method wrappers go on this object:
@@ -229,17 +230,17 @@ function loadLibraries() {
   gLibs.kernel = ctypes.open("kernel32.dll");
   KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime",
     ctypes.default_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
 
   declareESEFunctions();
 }
 
 function ESEDB(rootPath, dbPath, logPath) {
-  log.error("Created db");
+  log.info("Created db");
   this.rootPath = rootPath;
   this.dbPath = dbPath;
   this.logPath = logPath;
   this._references = 0;
   this._init();
 }
 
 ESEDB.prototype = {
@@ -306,31 +307,55 @@ ESEDB.prototype = {
       // Make sure caller knows we failed.
       throw ex;
     }
     gOpenDBs.set(this.dbPath, this);
   },
 
   checkForColumn(tableName, columnName) {
     if (!this._opened) {
-      throw "The database was closed!";
+      throw new Error("The database was closed!");
     }
 
     let columnInfo;
     try {
       columnInfo = this._getColumnInfo(tableName, [{name: columnName}]);
     } catch (ex) {
       return null;
     }
     return columnInfo[0];
   },
 
+  tableExists(tableName) {
+    if (!this._opened) {
+      throw new Error("The database was closed!");
+    }
+
+    let tableId = new ESE.JET_TABLEID();
+    let rv = ESE.ManualOpenTableW(this._sessionId, this._dbId, tableName, null,
+                                  0, 4 /* JET_bitTableReadOnly */,
+                                  tableId.address());
+    if (rv == -1305 /* JET_errObjectNotFound */) {
+      return false;
+    }
+    if (rv < 0) {
+      log.error("Got error " + rv + " calling OpenTableW");
+      throw new Error(convertESEError(rv));
+    }
+
+    if (rv > 0) {
+      log.error("Got warning " + rv + " calling OpenTableW");
+    }
+    ESE.FailSafeCloseTable(this._sessionId, tableId);
+    return true;
+  },
+
   tableItems: function*(tableName, columns) {
     if (!this._opened) {
-      throw "The database was closed!";
+      throw new Error("The database was closed!");
     }
 
     let tableOpened = false;
     let tableId;
     try {
       tableId = this._openTable(tableName);
       tableOpened = true;
 
@@ -381,38 +406,57 @@ ESEDB.prototype = {
       let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
       // size on the column is in bytes, 2 bytes to a wchar, so:
       let charCount = column.dbSize >> 1;
       buffer = new wchar_tArray(charCount);
     } else if (column.type == "boolean") {
       buffer = new ctypes.uint8_t();
     } else if (column.type == "date") {
       buffer = new KERNEL.FILETIME();
+    } else if (column.type == "guid") {
+      let byteArray = ctypes.ArrayType(ctypes.uint8_t);
+      buffer = new byteArray(column.dbSize);
     } else {
-      throw "Unknown type " + column.type;
+      throw new Error("Unknown type " + column.type);
     }
     return [buffer, buffer.constructor.size];
   },
 
   _convertResult(column, buffer, err) {
     if (err != 0) {
       if (err == 1004) {
         // Deal with null values:
         buffer = null;
       } else {
         Cu.reportError("Unexpected JET error: " + err + ";" + " retrieving value for column " + column.name);
-        throw this.convertError(err);
+        throw new Error(convertESEError(err));
       }
     }
     if (column.type == "string") {
       return buffer ? buffer.readString() : "";
     }
     if (column.type == "boolean") {
       return buffer ? (255 == buffer.value) : false;
     }
+    if (column.type == "guid") {
+      if (buffer.length != 16) {
+        Cu.reportError("Buffer size for guid field " + column.id + " should have been 16!");
+        return "";
+      }
+      let rv = "{";
+      for (let i = 0; i < 16; i++) {
+        if (i == 4 || i == 6 || i == 8 || i == 10) {
+          rv += "-";
+        }
+        let byteValue = buffer.addressOfElement(i).contents;
+        // Ensure there's a leading 0
+        rv += ("0" + byteValue.toString(16)).substr(-2);
+      }
+      return rv + "}";
+    }
     if (column.type == "date") {
       if (!buffer) {
         return null;
       }
       let systemTime = new KERNEL.SYSTEMTIME();
       let result = KERNEL.FileTimeToSystemTime(buffer.address(), systemTime.address());
       if (result == 0) {
         throw new Error(ctypes.winLastError);
@@ -452,16 +496,21 @@ ESEDB.prototype = {
           throw new Error("Invalid column type for column " + column.name +
                           "; expected bit type, got type " + getColTypeName(dbType));
         }
       } else if (column.type == "date") {
         if (dbType != COLUMN_TYPES.JET_coltypLongLong) {
           throw new Error("Invalid column type for column " + column.name +
                           "; expected long long type, got type " + getColTypeName(dbType));
         }
+      } else if (column.type == "guid") {
+        if (dbType != COLUMN_TYPES.JET_coltypGUID) {
+          throw new Error("Invalid column type for column " + column.name +
+                          "; expected guid type, got type " + getColTypeName(dbType));
+        }
       } else if (column.type) {
         throw new Error("Unknown column type " + column.type + " requested for column " +
                         column.name + ", don't know what to do.");
       }
 
       rv.push({name: column.name, id: columnInfoFromDB.columnid, type: column.type, dbSize, dbType});
     }
     return rv;
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -13,17 +13,82 @@ Cu.import("resource:///modules/MSMigrati
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ESEDBReader",
                                   "resource:///modules/ESEDBReader.jsm");
 
 const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" +
   "Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\" +
   "microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge";
-const kEdgeReadingListPath = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+const kEdgeDatabasePath = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+
+XPCOMUtils.defineLazyGetter(this, "gEdgeDatabase", function() {
+  let edgeDir = MSMigrationUtils.getEdgeLocalDataFolder();
+  if (!edgeDir) {
+    return null;
+  }
+  edgeDir.appendRelativePath(kEdgeDatabasePath);
+  if (!edgeDir.exists() || !edgeDir.isReadable() || !edgeDir.isDirectory()) {
+    return null;
+  }
+  let expectedLocation = edgeDir.clone();
+  expectedLocation.appendRelativePath("nouser1\\120712-0049\\DBStore\\spartan.edb");
+  if (expectedLocation.exists() && expectedLocation.isReadable() && expectedLocation.isFile()) {
+    return expectedLocation;
+  }
+  // We used to recurse into arbitrary subdirectories here, but that code
+  // went unused, so it likely isn't necessary, even if we don't understand
+  // where the magic folders above come from, they seem to be the same for
+  // everyone. Just return null if they're not there:
+  return null;
+});
+
+/**
+ * Get rows from a table in the Edge DB as an array of JS objects.
+ *
+ * @param {String}            tableName the name of the table to read.
+ * @param {String[]|function} columns   a list of column specifiers
+ *                                      (see ESEDBReader.jsm) or a function that
+ *                                      generates them based on the database
+ *                                      reference once opened.
+ * @param {function}          filterFn  a function that is called for each row.
+ *                                      Only rows for which it returns a truthy
+ *                                      value are included in the result.
+ * @returns {Array} An array of row objects.
+ */
+function readTableFromEdgeDB(tableName, columns, filterFn) {
+  let database;
+  let rows = [];
+  try {
+    let logFile = gEdgeDatabase.parent;
+    logFile.append("LogFiles");
+    database = ESEDBReader.openDB(gEdgeDatabase.parent, gEdgeDatabase, logFile);
+
+    if (typeof columns == "function") {
+      columns = columns(database);
+    }
+
+    let tableReader = database.tableItems(tableName, columns);
+    for (let row of tableReader) {
+      if (filterFn(row)) {
+        rows.push(row);
+      }
+    }
+  } catch (ex) {
+    Cu.reportError("Failed to extract items from table " + tableName + " in Edge database at " +
+                   gEdgeDatabase.path + " due to the following error: " + ex);
+    // Deliberately make this fail so we expose failure in the UI:
+    throw ex;
+  } finally {
+    if (database) {
+      ESEDBReader.closeDB(database);
+    }
+  }
+  return rows;
+}
 
 function EdgeTypedURLMigrator() {
 }
 
 EdgeTypedURLMigrator.prototype = {
   type: MigrationUtils.resourceTypes.HISTORY,
 
   get _typedURLs() {
@@ -83,92 +148,56 @@ EdgeTypedURLMigrator.prototype = {
   },
 }
 
 function EdgeReadingListMigrator() {
 }
 
 EdgeReadingListMigrator.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
-  _dbFile: null,
 
   get exists() {
-    this._dbFile = this._getDBFile();
-    return !!this._dbFile;
-  },
-
-  _getDBFile() {
-    let edgeDir = MSMigrationUtils.getEdgeLocalDataFolder();
-    if (!edgeDir) {
-      return null;
-    }
-    edgeDir.appendRelativePath(kEdgeReadingListPath);
-    if (!edgeDir.exists() || !edgeDir.isReadable() || !edgeDir.isDirectory()) {
-      return null;
-    }
-    let expectedLocation = edgeDir.clone();
-    expectedLocation.appendRelativePath("nouser1\\120712-0049\\DBStore\\spartan.edb");
-    if (expectedLocation.exists() && expectedLocation.isReadable() && expectedLocation.isFile()) {
-      return expectedLocation;
-    }
-    // We used to recurse into arbitrary subdirectories here, but that code
-    // went unused, so it likely isn't necessary, even if we don't understand
-    // where the magic folders above come from, they seem to be the same for
-    // everyone. Just return null if they're not there:
-    return null;
+    return !!gEdgeDatabase;
   },
 
   migrate(callback) {
     this._migrateReadingList(PlacesUtils.bookmarks.menuGuid).then(
       () => callback(true),
       ex => {
         Cu.reportError(ex);
         callback(false);
       }
     );
   },
 
   _migrateReadingList: Task.async(function*(parentGuid) {
-    let readingListItems = [];
-    let database;
-    try {
-      let logFile = this._dbFile.parent;
-      logFile.append("LogFiles");
-      database = ESEDBReader.openDB(this._dbFile.parent, this._dbFile, logFile);
+    let columnFn = db => {
       let columns = [
         {name: "URL", type: "string"},
         {name: "Title", type: "string"},
         {name: "AddedDate", type: "date"}
       ];
 
-      // Later versions have an isDeleted column:
-      let isDeletedColumn = database.checkForColumn("ReadingList", "isDeleted");
+      // Later versions have an IsDeleted column:
+      let isDeletedColumn = db.checkForColumn("ReadingList", "IsDeleted");
       if (isDeletedColumn && isDeletedColumn.dbType == ESEDBReader.COLUMN_TYPES.JET_coltypBit) {
-        columns.push({name: "isDeleted", type: "boolean"});
+        columns.push({name: "IsDeleted", type: "boolean"});
       }
+      return columns;
+    };
 
-      let tableReader = database.tableItems("ReadingList", columns);
-      for (let row of tableReader) {
-        if (!row.isDeleted) {
-          readingListItems.push(row);
-        }
-      }
-    } catch (ex) {
-      Cu.reportError("Failed to extract Edge reading list information from " +
-                     "the database at " + this._dbFile.path + " due to the following error: " + ex);
-      // Deliberately make this fail so we expose failure in the UI:
-      throw ex;
-    } finally {
-      if (database) {
-        ESEDBReader.closeDB(database);
-      }
-    }
+    let filterFn = row => {
+      return !row.IsDeleted;
+    };
+
+    let readingListItems = readTableFromEdgeDB("ReadingList", columnFn, filterFn);
     if (!readingListItems.length) {
       return;
     }
+
     let destFolderGuid = yield this._ensureReadingListFolder(parentGuid);
     let exceptionThrown;
     for (let item of readingListItems) {
       let dateAdded = item.AddedDate || new Date();
       yield PlacesUtils.bookmarks.insert({
         parentGuid: destFolderGuid, url: item.URL, title: item.Title, dateAdded
       }).catch(ex => {
         if (!exceptionThrown) {
@@ -187,24 +216,194 @@ EdgeReadingListMigrator.prototype = {
       let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
       let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
       this.__readingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid;
     }
     return this.__readingListFolderGuid;
   }),
 };
 
+function EdgeBookmarksMigrator() {
+}
+
+EdgeBookmarksMigrator.prototype = {
+  type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+  get TABLE_NAME() { return "Favorites" },
+
+  get exists() {
+    if ("_exists" in this) {
+      return this._exists;
+    }
+    return this._exists = (!!gEdgeDatabase && this._checkTableExists());
+  },
+
+  _checkTableExists() {
+    let database;
+    let rv;
+    try {
+      let logFile = gEdgeDatabase.parent;
+      logFile.append("LogFiles");
+      database = ESEDBReader.openDB(gEdgeDatabase.parent, gEdgeDatabase, logFile);
+
+      rv = database.tableExists(this.TABLE_NAME);
+    } catch (ex) {
+      Cu.reportError("Failed to check for table " + tableName + " in Edge database at " +
+                     gEdgeDatabase.path + " due to the following error: " + ex);
+      return false;
+    } finally {
+      if (database) {
+        ESEDBReader.closeDB(database);
+      }
+    }
+    return rv;
+  },
+
+  migrate(callback) {
+    this._migrateBookmarks(PlacesUtils.bookmarks.menuGuid).then(
+      () => callback(true),
+      ex => {
+        Cu.reportError(ex);
+        callback(false);
+      }
+    );
+  },
+
+  _migrateBookmarks: Task.async(function*(rootGuid) {
+    let {bookmarks, folderMap} = this._fetchBookmarksFromDB();
+    if (!bookmarks.length) {
+      return;
+    }
+    yield this._importBookmarks(bookmarks, folderMap, rootGuid);
+  }),
+
+  _importBookmarks: Task.async(function*(bookmarks, folderMap, rootGuid) {
+    if (!MigrationUtils.isStartupMigration) {
+      rootGuid =
+        yield MigrationUtils.createImportedBookmarksFolder("Edge", rootGuid);
+    }
+
+    let exceptionThrown;
+    for (let bookmark of bookmarks) {
+      // If this is a folder, we might have created it already to put other bookmarks in.
+      if (bookmark.IsFolder && bookmark._guid) {
+        continue;
+      }
+
+      // If this is a folder, just create folders up to and including that folder.
+      // Otherwise, create folders until we have a parent for this bookmark.
+      // This avoids duplicating logic for the bookmarks bar.
+      let folderId = bookmark.IsFolder ? bookmark.ItemId : bookmark.ParentId;
+      let parentGuid = yield this._getGuidForFolder(folderId, folderMap, rootGuid).catch(ex => {
+        if (!exceptionThrown) {
+          exceptionThrown = ex;
+        }
+        Cu.reportError(ex);
+      });
+
+      // If this was a folder, we're done with this item
+      if (bookmark.IsFolder) {
+        continue;
+      }
+
+      if (!parentGuid) {
+        // If we couldn't sort out a parent, fall back to importing on the root:
+        parentGuid = rootGuid;
+      }
+      let placesInfo = {
+        parentGuid,
+        url: bookmark.URL,
+        dateAdded: bookmark.DateUpdated || new Date(),
+        title: bookmark.Title,
+      }
+
+      yield PlacesUtils.bookmarks.insert(placesInfo).catch(ex => {
+        if (!exceptionThrown) {
+          exceptionThrown = ex;
+        }
+        Cu.reportError(ex);
+      });
+    }
+
+    if (exceptionThrown) {
+      throw exceptionThrown;
+    }
+  }),
+
+  _fetchBookmarksFromDB() {
+    let folderMap = new Map();
+    let columns = [
+      {name: "URL", type: "string"},
+      {name: "Title", type: "string"},
+      {name: "DateUpdated", type: "date"},
+      {name: "IsFolder", type: "boolean"},
+      {name: "IsDeleted", type: "boolean"},
+      {name: "ParentId", type: "guid"},
+      {name: "ItemId", type: "guid"}
+    ];
+    let filterFn = row => {
+      if (row.IsDeleted) {
+        return false;
+      }
+      if (row.IsFolder) {
+        folderMap.set(row.ItemId, row);
+      }
+      return true;
+    }
+    let bookmarks = readTableFromEdgeDB(this.TABLE_NAME, columns, filterFn);
+    return {bookmarks, folderMap};
+  },
+
+  _getGuidForFolder: Task.async(function*(folderId, folderMap, rootGuid) {
+    // If the folderId is not known as a folder in the folder map, we assume
+    // we just need the root
+    if (!folderMap.has(folderId)) {
+      return rootGuid;
+    }
+    let folder = folderMap.get(folderId);
+    // If the folder already has a places guid, just return that.
+    if (folder._guid) {
+      return folder._guid;
+    }
+
+    // Hacks! The bookmarks bar is special:
+    if (folder.Title == "_Favorites_Bar_") {
+      let toolbarGuid = PlacesUtils.bookmarks.toolbarGuid;
+      if (!MigrationUtils.isStartupMigration) {
+        toolbarGuid =
+          yield MigrationUtils.createImportedBookmarksFolder("Edge", toolbarGuid);
+      }
+      return folder._guid = toolbarGuid;
+    }
+    // Otherwise, get the right parent guid recursively:
+    let parentGuid = yield this._getGuidForFolder(folder.ParentId, folderMap, rootGuid);
+    let folderInfo = {
+      title: folder.Title,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      dateAdded: folder.DateUpdated || new Date(),
+      parentGuid,
+    };
+    // and add ourselves as a kid, and return the guid we got.
+    let parentBM = yield PlacesUtils.bookmarks.insert(folderInfo);
+    return folder._guid = parentBM.guid;
+  }),
+}
+
 function EdgeProfileMigrator() {
 }
 
 EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 EdgeProfileMigrator.prototype.getResources = function() {
+  let bookmarksMigrator = new EdgeBookmarksMigrator();
+  if (!bookmarksMigrator.exists) {
+    bookmarksMigrator = MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE);
+  }
   let resources = [
-    MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+    bookmarksMigrator,
     MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
     new EdgeTypedURLMigrator(),
     new EdgeReadingListMigrator(),
   ];
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
--- a/browser/components/newtab/NewTabPrefsProvider.jsm
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -6,17 +6,17 @@
 this.EXPORTED_SYMBOLS = ["NewTabPrefsProvider"];
 
 const {interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
-  const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
+  const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
   return EventEmitter;
 });
 
 // Supported prefs and data type
 const gPrefsMap = new Map([
   ["browser.newtabpage.remote", "bool"],
   ["browser.newtabpage.enabled", "bool"],
   ["browser.newtabpage.enhanced", "bool"],
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -412,18 +412,22 @@
         else {
           // We are not dragging over a folder.
           // Clear out old _overFolder information.
           this._overFolder.clear();
         }
 
         // Autoscroll the popup strip if we drag over the scroll buttons.
         let anonid = event.originalTarget.getAttribute('anonid');
-        let scrollDir = anonid == "scrollbutton-up" ? -1 :
-                        anonid == "scrollbutton-down" ? 1 : 0;
+        let scrollDir = 0;
+        if (anonid == "scrollbutton-up") {
+          scrollDir = -1;
+        } else if (anonid == "scrollbutton-down") {
+          scrollDir = 1;
+        }
         if (scrollDir != 0) {
           this._scrollBox.scrollByIndex(scrollDir, false);
         }
 
         // Check if we should hide the drop indicator for this target.
         if (dropPoint.folderElt || this._hideDropIndicator(event)) {
           this._indicatorBar.hidden = true;
           event.preventDefault();
--- a/browser/extensions/pocket/content/pktApi.jsm
+++ b/browser/extensions/pocket/content/pktApi.jsm
@@ -537,17 +537,17 @@ var pktApi = (function() {
                 for (var tagKey in usedTagsObject) {
                     usedTagsObjectArray.push(usedTagsObject[tagKey]);
                 }
 
                 // Sort usedTagsObjectArray based on timestamp
                 usedTagsObjectArray.sort(function(usedTagA, usedTagB) {
                     var a = usedTagA.timestamp;
                     var b = usedTagB.timestamp;
-                    return a < b ? -1 : a > b ? 1 : 0;
+                    return a - b;
                 });
 
                 // Get all keys tags
                 for (var j = 0; j < usedTagsObjectArray.length; j++) {
                     usedTags.push(usedTagsObjectArray[j].tag);
                 }
 
                 // Reverse to set the last recent used tags to the front
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -158,23 +158,41 @@ this.TabCrashHandler = {
     let {
       includeURL,
       comments,
       email,
       emailMe,
       URL,
     } = message.data;
 
+    let extraExtraKeyVals = {
+      "Comments": comments,
+      "Email": email,
+      "URL": URL,
+    };
+
+    // For the entries in extraExtraKeyVals, we only want to submit the
+    // extra data values where they are not the empty string.
+    for (let key in extraExtraKeyVals) {
+      let val = extraExtraKeyVals[key].trim();
+      if (!val) {
+        delete extraExtraKeyVals[key];
+      }
+    }
+
+    // URL is special, since it's already been written to extra data by
+    // default. In order to make sure we don't send it, we overwrite it
+    // with the empty string.
+    if (!includeURL) {
+      extraExtraKeyVals["URL"] = "";
+    }
+
     CrashSubmit.submit(dumpID, {
       recordSubmission: true,
-      extraExtraKeyVals: {
-        Comments: comments,
-        Email: email,
-        URL: URL,
-      },
+      extraExtraKeyVals,
     }).then(null, Cu.reportError);
 
     this.prefs.setBoolPref("sendReport", true);
     this.prefs.setBoolPref("includeURL", includeURL);
     this.prefs.setBoolPref("emailMe", emailMe);
     if (emailMe) {
       this.prefs.setCharPref("email", email);
     } else {
--- a/browser/tools/mozscreenshots/head.js
+++ b/browser/tools/mozscreenshots/head.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {AddonWatcher} = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
 const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
 
 function setup() {
-  requestLongerTimeout(10);
+  requestLongerTimeout(20);
 
   info("Checking for mozscreenshots extension");
   return new Promise((resolve) => {
     AddonManager.getAddonByID("mozscreenshots@mozilla.org", function(aAddon) {
       isnot(aAddon, null, "The mozscreenshots extension should be installed");
       AddonWatcher.ignoreAddonPermanently(aAddon.id);
       resolve();
     });
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
@@ -8,18 +8,20 @@ this.EXPORTED_SYMBOLS = ["TestRunner"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 const defaultSetNames = ["TabsInTitlebar", "Tabs", "WindowSize", "Toolbars", "LightweightThemes"];
 const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
+
 Cu.import("chrome://mozscreenshots/content/Screenshot.jsm");
 
 // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL = "extensions.mozscreenshots@mozilla.org.loglevel";
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
   let consoleOptions = {
@@ -141,19 +143,25 @@ this.TestRunner = {
 
   _performCombo: function*(combo) {
     let paddedComboIndex = padLeft(this.currentComboIndex + 1, String(this.combos.length).length);
     log.info("Combination " + paddedComboIndex + "/" + this.combos.length + ": " +
              this._comboName(combo).substring(1));
 
     function changeConfig(config) {
       log.debug("calling " + config.name);
-      let promise = config.applyConfig();
+      let promise = Promise.resolve(config.applyConfig());
       log.debug("called " + config.name);
-      return promise;
+      // Add a default timeout of 500ms to avoid conflicts when configurations
+      // try to apply at the same time. e.g WindowSize and TabsInTitlebar
+      return promise.then(() => {
+        return new Promise((resolve) => {
+          setTimeout(resolve, 500);
+        });
+      });
     }
 
     try {
       // First go through and actually apply all of the configs
       for (let i = 0; i < combo.length; i++) {
         let config = combo[i];
         if (!this._lastCombo || config !== this._lastCombo[i]) {
           log.debug("promising", config.name);
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
@@ -5,17 +5,17 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["DevTools"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://devtools/client/framework/gDevTools.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 
 function getTargetForSelectedTab() {
   let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
   let target = TargetFactory.forTab(browserWindow.gBrowser.selectedTab);
   return target;
 }
 
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
@@ -10,22 +10,22 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 
 this.LightweightThemes = {
   init(libDir) {
-    // convert -size 3000x200 canvas:black black_theme.png
+    // convert -size 3000x200 canvas:#333 black_theme.png
     let blackImage = libDir.clone();
     blackImage.append("black_theme.png");
     this._blackImageURL = Services.io.newFileURI(blackImage).spec;
 
-    // convert -size 3000x200 canvas:white white_theme.png
+    // convert -size 3000x200 canvas:#eee white_theme.png
     let whiteImage = libDir.clone();
     whiteImage.append("white_theme.png");
     this._whiteImageURL = Services.io.newFileURI(whiteImage).spec;
   },
 
   configurations: {
     noLWT: {
       applyConfig: Task.async(function*() {
@@ -35,17 +35,17 @@ this.LightweightThemes = {
 
     darkLWT: {
       applyConfig() {
         LightweightThemeManager.setLocalTheme({
           id:          "black",
           name:        "black",
           headerURL:   LightweightThemes._blackImageURL,
           footerURL:   LightweightThemes._blackImageURL,
-          textcolor:   "#ffffff",
+          textcolor:   "#eeeeee",
           accentcolor: "#111111",
         });
 
         // Wait for LWT listener
         return new Promise(resolve => {
           setTimeout(() => {
             resolve("darkLWT");
           }, 500);
@@ -57,17 +57,17 @@ this.LightweightThemes = {
 
     lightLWT: {
       applyConfig() {
         LightweightThemeManager.setLocalTheme({
           id:          "white",
           name:        "white",
           headerURL:   LightweightThemes._whiteImageURL,
           footerURL:   LightweightThemes._whiteImageURL,
-          textcolor:   "#000000",
+          textcolor:   "#111111",
           accentcolor: "#eeeeee",
         });
         // Wait for LWT listener
         return new Promise(resolve => {
           setTimeout(() => {
             resolve("lightLWT");
           }, 500);
         });
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
@@ -59,17 +59,18 @@ this.Tabs = {
     },
 
     twoPinnedWithOverflow: {
       applyConfig: Task.async(function*() {
         fiveTabsHelper();
 
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         browserWindow.gBrowser.loadTabs([
-          "about:addons",
+          PREFS_TAB,
+          CUST_TAB,
           "about:home",
           DEFAULT_FAVICON_TAB,
           "about:newtab",
           "about:addons",
           "about:home",
           DEFAULT_FAVICON_TAB,
           "about:newtab",
           "about:addons",
@@ -88,22 +89,20 @@ this.Tabs = {
           "about:home",
           DEFAULT_FAVICON_TAB,
           "about:newtab",
           "about:addons",
           "about:home",
           DEFAULT_FAVICON_TAB,
           "about:newtab",
         ], true, true);
-        let tab = browserWindow.gBrowser.addTab(PREFS_TAB);
-        browserWindow.gBrowser.pinTab(tab);
-        tab = browserWindow.gBrowser.addTab(CUST_TAB);
-        browserWindow.gBrowser.pinTab(tab);
-        browserWindow.gBrowser.selectTabAtIndex(4);
-        hoverTab(browserWindow.gBrowser.tabs[6]);
+        browserWindow.gBrowser.pinTab(browserWindow.gBrowser.tabs[1]);
+        browserWindow.gBrowser.pinTab(browserWindow.gBrowser.tabs[2]);
+        browserWindow.gBrowser.selectTabAtIndex(3);
+        hoverTab(browserWindow.gBrowser.tabs[5]);
         yield new Promise((resolve, reject) => {
           setTimeout(resolve, 3000);
         });
       }),
     },
   },
 };
 
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/WindowSize.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/WindowSize.jsm
@@ -6,56 +6,63 @@
 
 this.EXPORTED_SYMBOLS = ["WindowSize"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://testing-common/BrowserTestUtils.jsm");
 
 this.WindowSize = {
 
   init(libDir) {
     Services.prefs.setBoolPref("browser.fullscreen.autohide", false);
   },
 
   configurations: {
     maximized: {
       applyConfig: Task.async(function*() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        browserWindow.fullScreen = false;
+        yield toggleFullScreen(browserWindow, false);
 
         // Wait for the Lion fullscreen transition to end as there doesn't seem to be an event
         // and trying to maximize while still leaving fullscreen doesn't work.
         yield new Promise((resolve, reject) => {
           setTimeout(function waitToLeaveFS() {
             browserWindow.maximize();
             resolve();
           }, 5000);
         });
       }),
     },
 
     normal: {
       applyConfig: Task.async(function*() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        browserWindow.fullScreen = false;
+        yield toggleFullScreen(browserWindow, false);
         browserWindow.restore();
         yield new Promise((resolve, reject) => {
           setTimeout(resolve, 5000);
         });
       }),
     },
 
     fullScreen: {
       applyConfig: Task.async(function*() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        browserWindow.fullScreen = true;
+        yield toggleFullScreen(browserWindow, true);
         // OS X Lion fullscreen transition takes a while
         yield new Promise((resolve, reject) => {
           setTimeout(resolve, 5000);
         });
       }),
     },
-
   },
 };
+
+function toggleFullScreen(browserWindow, wantsFS) {
+  browserWindow.fullScreen = wantsFS;
+  return BrowserTestUtils.waitForCondition(() => {
+    return wantsFS == browserWindow.document.documentElement.hasAttribute("inFullscreen");
+  }, "waiting for @inFullscreen change");
+}
index 810420d967a6aa6211494f67cab112b161e300d2..78e47207b87a346ebd8c958e91286d5d6abfb84f
GIT binary patch
literal 977
zc%17D@N?(olHy`uVBq!ia0y~y;NAgbpI`wJ3^P{C%?DB}>5jgR3=A9lx&I`x0{IHb
z9znhg3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!Ib3f?!v%tWn{M)
z$YU?@^mS!_z{Db`%4Y0#vH>W6)6>N<q~g}wD~_BD20RB2RIRGdH#L-Kv1YAI6>FID
zQ<mXC^)o(!ly?k{qtqxhbSU<-&dnaD&unP}rhC;A*NBpo#FA92<f2p{#b9J$Xr^mm
zq-$UjVrXt<Y;I)$<XTu67&sScFGtako1c=IR*74Kk^W?9P+s+P^>bP0l+XkKW^zk#
index ee0ef460df4c9b82a548628d756ac93ba4e3f5f0..6b0c501a1bd88f62e9b7f0beb7892874a50334e1
GIT binary patch
literal 977
zc%17D@N?(olHy`uVBq!ia0y~y;NAgbpI`wJ3^P{C%?DB}>5jgR3=A9lx&I`x0{IHb
z9znhg3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!Ib3f?!v%tWn{M)
z$YU?@^mS!_z{Db`#@TVD{Sr|Arl*TzNX4zUR~$JR40sM4_$40AYicObV$E8aD%LRP
zr!2#P>SufcDeo8@N2yV2=uqq*Lv{%)G&#BknC?|eTq8<S5=&C8l8aJ-6oZk0p_#6M
zk*<MBh@rWav4xd^rLF;xG;lFwIE<npH$NpatrE8e)0`dkpuFno>gTe~DWM4ff(lFd
--- a/devtools/client/animationinspector/animation-panel.js
+++ b/devtools/client/animationinspector/animation-panel.js
@@ -37,16 +37,19 @@ var AnimationsPanel = {
     this.errorMessageEl = $("#error-message");
     this.pickerButtonEl = $("#element-picker");
     this.toggleAllButtonEl = $("#toggle-all");
     this.playTimelineButtonEl = $("#pause-resume-timeline");
     this.rewindTimelineButtonEl = $("#rewind-timeline");
     this.timelineCurrentTimeEl = $("#timeline-current-time");
     this.rateSelectorEl = $("#timeline-rate");
 
+    this.rewindTimelineButtonEl.setAttribute("title",
+      L10N.getStr("timeline.rewindButtonTooltip"));
+
     // If the server doesn't support toggling all animations at once, hide the
     // whole global toolbar.
     if (!AnimationsController.traits.hasToggleAll) {
       $("#global-toolbar").style.display = "none";
     }
 
     // Binding functions that need to be called in scope.
     for (let functionName of ["onPickerStarted", "onPickerStopped",
@@ -208,16 +211,22 @@ var AnimationsPanel = {
   },
 
   onTimelineDataChanged: function(e, data) {
     this.timelineData = data;
     let {isMoving, isUserDrag, time} = data;
 
     this.playTimelineButtonEl.classList.toggle("paused", !isMoving);
 
+    let l10nPlayProperty = isMoving ? "timeline.resumedButtonTooltip" :
+                                      "timeline.pausedButtonTooltip";
+
+    this.playTimelineButtonEl.setAttribute("title",
+      L10N.getStr(l10nPlayProperty));
+
     // If the timeline data changed as a result of the user dragging the
     // scrubber, then pause all animations and set their currentTimes.
     // (Note that we want server-side requests to be sequenced, so we only do
     // this after the previous currentTime setting was done).
     if (isUserDrag && !this.setCurrentTimeAllPromise) {
       this.setCurrentTimeAllPromise =
         AnimationsController.setCurrentTimeAll(time, true)
                             .catch(error => console.error(error))
--- a/devtools/client/animationinspector/components/rate-selector.js
+++ b/devtools/client/animationinspector/components/rate-selector.js
@@ -23,17 +23,20 @@ function RateSelector() {
 
 exports.RateSelector = RateSelector;
 
 RateSelector.prototype = {
   init: function(containerEl) {
     this.selectEl = createNode({
       parent: containerEl,
       nodeType: "select",
-      attributes: {"class": "devtools-button"}
+      attributes: {
+        "class": "devtools-button",
+        "title": L10N.getStr("timeline.rateSelectorTooltip")
+      }
     });
 
     this.selectEl.addEventListener("change", this.onRateChanged);
   },
 
   destroy: function() {
     this.selectEl.removeEventListener("change", this.onRateChanged);
     this.selectEl.remove();
--- a/devtools/client/debugger/debugger.xul
+++ b/devtools/client/debugger/debugger.xul
@@ -490,12 +490,9 @@
          noautofocus="true"
          consumeoutsideclicks="false">
     <vbox>
       <label id="conditional-breakpoint-panel-description"
              value="&debuggerUI.condBreakPanelTitle;"/>
       <textbox id="conditional-breakpoint-panel-textbox"/>
     </vbox>
   </panel>
-
-  <script type="text/javascript" src="resource://gre/browser/modules/devtools/debugger/mocks.js"/>
-  <script type="text/javascript" src="resource://gre/browser/modules/devtools/debugger/mock-init.js"/>
 </window>
--- a/devtools/client/inspector/fonts/test/browser.ini
+++ b/devtools/client/inspector/fonts/test/browser.ini
@@ -7,9 +7,8 @@ support-files =
   ostrich-black.ttf
   ostrich-regular.ttf
   head.js
 
 [browser_fontinspector.js]
 [browser_fontinspector_edit-previews.js]
 [browser_fontinspector_edit-previews-show-all.js]
 [browser_fontinspector_theme-change.js]
-skip-if = e10s && debug && os == 'win'
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -302,18 +302,18 @@ LayoutView.prototype = {
     let { property } = dimension;
     let session = new EditingSession(this.doc, this.elementRules);
     let initialValue = session.getProperty(property);
 
     let editor = new InplaceEditor({
       element: element,
       initial: initialValue,
 
-      start: editor => {
-        editor.elt.parentNode.classList.add("layout-editing");
+      start: self => {
+        self.elt.parentNode.classList.add("layout-editing");
       },
 
       change: value => {
         if (NUMERIC.test(value)) {
           value += "px";
         }
 
         let properties = [
@@ -452,17 +452,17 @@ LayoutView.prototype = {
   /**
    * Compute the dimensions of the node and update the values in
    * the layoutview/view.xhtml document.
    * @return a promise that will be resolved when complete.
    */
   update: function() {
     let lastRequest = Task.spawn((function*() {
       if (!this.isViewVisibleAndNodeValid()) {
-        return;
+        return null;
       }
 
       let node = this.inspector.selection.nodeFront;
       let layout = yield this.inspector.pageStyle.getLayout(node, {
         autoMargins: this.isActive
       });
       let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
 
@@ -541,17 +541,18 @@ LayoutView.prototype = {
         this.sizeLabel.textContent = newValue;
       }
 
       this.elementRules = styleEntries.map(e => e.rule);
 
       this.inspector.emit("layoutview-updated");
     }).bind(this)).then(null, console.error);
 
-    return this._lastRequest = lastRequest;
+    this._lastRequest = lastRequest;
+    return this._lastRequest;
   },
 
   /**
    * Update the text in the tooltip shown when hovering over a value to provide
    * information about the source CSS rule that sets this value.
    * @param {DOMNode} el The element that will receive the tooltip.
    * @param {String} property The name of the CSS property for the tooltip.
    * @param {Array} rules An array of applied rules retrieved by
@@ -582,17 +583,17 @@ LayoutView.prototype = {
     }
     el.setAttribute("title", title);
   },
 
   /**
    * Show the box-model highlighter on the currently selected element
    * @param {Object} options Options passed to the highlighter actor
    */
-  showBoxModel: function(options={}) {
+  showBoxModel: function(options = {}) {
     let toolbox = this.inspector.toolbox;
     let nodeFront = this.inspector.selection.nodeFront;
 
     toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
   },
 
   /**
    * Hide the box-model highlighter on the currently selected element
--- a/devtools/client/inspector/layout/test/browser.ini
+++ b/devtools/client/inspector/layout/test/browser.ini
@@ -1,11 +1,10 @@
 [DEFAULT]
 tags = devtools
-skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
   doc_layout_iframe1.html
   doc_layout_iframe2.html
   head.js
 
 [browser_layout.js]
 [browser_layout_editablemodel.js]
--- a/devtools/client/inspector/layout/test/browser_layout.js
+++ b/devtools/client/inspector/layout/test/browser_layout.js
@@ -122,42 +122,47 @@ var res2 = [
   },
   {
     selector: ".layout-border.layout-right > span",
     value: 10
   },
 ];
 
 add_task(function*() {
-  let style = "div { position: absolute; top: 42px; left: 42px; height: 100.111px; width: 100px; border: 10px solid black; padding: 20px; margin: 30px auto;}";
+  let style = "div { position: absolute; top: 42px; left: 42px; " +
+              "height: 100.111px; width: 100px; border: 10px solid black; " +
+              "padding: 20px; margin: 30px auto;}";
   let html = "<style>" + style + "</style><div></div>";
 
   yield addTab("data:text/html," + encodeURIComponent(html));
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view, testActor} = yield openLayoutView();
   yield selectNode("div", inspector);
 
-  yield runTests(inspector, view);
+  yield testInitialValues(inspector, view);
+  yield testChangingValues(inspector, view, testActor);
 });
 
-addTest("Test that the initial values of the box model are correct",
-function*(inspector, view) {
+function* testInitialValues(inspector, view) {
+  info("Test that the initial values of the box model are correct");
   let viewdoc = view.doc;
 
   for (let i = 0; i < res1.length; i++) {
     let elt = viewdoc.querySelector(res1[i].selector);
-    is(elt.textContent, res1[i].value, res1[i].selector + " has the right value.");
+    is(elt.textContent, res1[i].value,
+       res1[i].selector + " has the right value.");
   }
-});
+}
 
-addTest("Test that changing the document updates the box model",
-function*(inspector, view) {
+function* testChangingValues(inspector, view, testActor) {
+  info("Test that changing the document updates the box model");
   let viewdoc = view.doc;
 
   let onUpdated = waitForUpdate(inspector);
-  inspector.selection.node.style.height = "150px";
-  inspector.selection.node.style.paddingRight = "50px";
+  yield testActor.setAttribute("div", "style",
+                               "height:150px;padding-right:50px;");
   yield onUpdated;
 
   for (let i = 0; i < res2.length; i++) {
     let elt = viewdoc.querySelector(res2[i].selector);
-    is(elt.textContent, res2[i].value, res2[i].selector + " has the right value after style update.");
+    is(elt.textContent, res2[i].value,
+       res2[i].selector + " has the right value after style update.");
   }
-});
+}
--- a/devtools/client/inspector/layout/test/browser_layout_editablemodel.js
+++ b/devtools/client/inspector/layout/test/browser_layout_editablemodel.js
@@ -14,149 +14,164 @@ const TEST_URI = "<style>" +
   "#div3 { padding: 2em; }" +
   "#div4 { margin: 1px; }" +
   "#div5 { margin: 1px; }" +
   "</style>" +
   "<div id='div1'></div><div id='div2'></div>" +
   "<div id='div3'></div><div id='div4'></div>" +
   "<div id='div5'></div>";
 
-function getStyle(node, property) {
-  return node.style.getPropertyValue(property);
-}
-
 add_task(function*() {
   yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view, testActor} = yield openLayoutView();
 
-  yield runTests(inspector, view);
+  yield testEditingMargins(inspector, view, testActor);
+  yield testKeyBindings(inspector, view, testActor);
+  yield testEscapeToUndo(inspector, view, testActor);
+  yield testDeletingValue(inspector, view, testActor);
+  yield testRefocusingOnClick(inspector, view, testActor);
+  yield testBluringOnClick(inspector, view);
 });
 
-addTest("Test that editing margin dynamically updates the document, pressing escape cancels the changes",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
-  is(getStyle(node, "margin-top"), "", "Should be no margin-top on the element.")
+function* testEditingMargins(inspector, view, testActor) {
+  info("Test that editing margin dynamically updates the document, pressing " +
+       "escape cancels the changes");
+
+  is((yield getStyle(testActor, "#div1", "margin-top")), "",
+     "Should be no margin-top on the element.");
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-margin.layout-top > span");
   is(span.textContent, 5, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "5px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("3", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "margin-top"), "3px", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div1", "margin-top")), "3px",
+     "Should have updated the margin.");
 
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "margin-top"), "", "Should be no margin-top on the element.")
+  is((yield getStyle(testActor, "#div1", "margin-top")), "",
+     "Should be no margin-top on the element.");
   is(span.textContent, 5, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that arrow keys work correctly and pressing enter commits the changes",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
-  is(getStyle(node, "margin-left"), "", "Should be no margin-top on the element.")
+function* testKeyBindings(inspector, view, testActor) {
+  info("Test that arrow keys work correctly and pressing enter commits the " +
+       "changes");
+
+  is((yield getStyle(testActor, "#div1", "margin-left")), "",
+     "Should be no margin-top on the element.");
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-margin.layout-left > span");
   is(span.textContent, 10, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "10px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("VK_UP", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "11px", "Should have the right value in the editor.");
-  is(getStyle(node, "margin-left"), "11px", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div1", "margin-left")), "11px",
+     "Should have updated the margin.");
 
   EventUtils.synthesizeKey("VK_DOWN", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "10px", "Should have the right value in the editor.");
-  is(getStyle(node, "margin-left"), "10px", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div1", "margin-left")), "10px",
+     "Should have updated the margin.");
 
   EventUtils.synthesizeKey("VK_UP", { shiftKey: true }, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "20px", "Should have the right value in the editor.");
-  is(getStyle(node, "margin-left"), "20px", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div1", "margin-left")), "20px",
+     "Should have updated the margin.");
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
+  is((yield getStyle(testActor, "#div1", "margin-left")), "20px",
+     "Should be the right margin-top on the element.");
   is(span.textContent, 20, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that deleting the value removes the property but escape undoes that",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
-  is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
+function* testEscapeToUndo(inspector, view, testActor) {
+  info("Test that deleting the value removes the property but escape undoes " +
+       "that");
+
+  is((yield getStyle(testActor, "#div1", "margin-left")), "20px",
+     "Should be the right margin-top on the element.");
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-margin.layout-left > span");
   is(span.textContent, 20, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "20px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "", "Should have the right value in the editor.");
-  is(getStyle(node, "margin-left"), "", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div1", "margin-left")), "",
+     "Should have updated the margin.");
 
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "margin-left"), "20px", "Should be the right margin-top on the element.")
+  is((yield getStyle(testActor, "#div1", "margin-left")), "20px",
+     "Should be the right margin-top on the element.");
   is(span.textContent, 20, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that deleting the value removes the property",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
+function* testDeletingValue(inspector, view, testActor) {
+  info("Test that deleting the value removes the property");
 
-  node.style.marginRight = "15px";
+  yield setStyle(testActor, "#div1", "marginRight", "15px");
   yield waitForUpdate(inspector);
 
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-margin.layout-right > span");
   is(span.textContent, 15, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "15px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "", "Should have the right value in the editor.");
-  is(getStyle(node, "margin-right"), "", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div1", "margin-right")), "",
+     "Should have updated the margin.");
 
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "margin-right"), "", "Should be the right margin-top on the element.")
+  is((yield getStyle(testActor, "#div1", "margin-right")), "",
+     "Should be the right margin-top on the element.");
   is(span.textContent, 10, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that clicking in the editor input does not remove focus",
-function*(inspector, view) {
-  let node = content.document.getElementById("div4");
+function* testRefocusingOnClick(inspector, view, testActor) {
+  info("Test that clicking in the editor input does not remove focus");
 
   yield selectNode("#div4", inspector);
 
   let span = view.doc.querySelector(".layout-margin.layout-top > span");
   is(span.textContent, 1, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
@@ -167,27 +182,27 @@ function*(inspector, view) {
   is(editor, view.doc.activeElement,
     "Inplace editor input should still have focus.");
 
   info("Check the input can still be used as expected");
   EventUtils.synthesizeKey("VK_UP", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "2px", "Should have the right value in the editor.");
-  is(getStyle(node, "margin-top"), "2px", "Should have updated the margin.");
+  is((yield getStyle(testActor, "#div4", "margin-top")), "2px",
+     "Should have updated the margin.");
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "margin-top"), "2px",
-    "Should be the right margin-top on the element.");
+  is((yield getStyle(testActor, "#div4", "margin-top")), "2px",
+     "Should be the right margin-top on the element.");
   is(span.textContent, 2, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that clicking outside the editor blurs it",
-function*(inspector, view) {
-  let node = content.document.getElementById("div5");
+function* testBluringOnClick(inspector, view) {
+  info("Test that clicking outside the editor blurs it");
 
   yield selectNode("#div5", inspector);
 
   let span = view.doc.querySelector(".layout-margin.layout-top > span");
   is(span.textContent, 1, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
@@ -195,9 +210,9 @@ function*(inspector, view) {
 
   info("Click next to the opened editor input.");
   let rect = editor.getBoundingClientRect();
   EventUtils.synthesizeMouse(editor, rect.width + 10, rect.height / 2, {},
     view.doc.defaultView);
 
   is(view.doc.querySelector(".styleinspector-propertyeditor"), null,
     "Inplace editor has been removed.");
-});
+}
--- a/devtools/client/inspector/layout/test/browser_layout_editablemodel_allproperties.js
+++ b/devtools/client/inspector/layout/test/browser_layout_editablemodel_allproperties.js
@@ -9,135 +9,138 @@
 const TEST_URI = "<style>" +
   "div { margin: 10px; padding: 3px }" +
   "#div1 { margin-top: 5px }" +
   "#div2 { border-bottom: 1em solid black; }" +
   "#div3 { padding: 2em; }" +
   "</style>" +
   "<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
 
-function getStyle(node, property) {
-  return node.style.getPropertyValue(property);
-}
-
 add_task(function*() {
   yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view, testActor} = yield openLayoutView();
 
-  yield runTests(inspector, view);
+  yield testEditing(inspector, view, testActor);
+  yield testEditingAndCanceling(inspector, view, testActor);
+  yield testDeleting(inspector, view, testActor);
+  yield testDeletingAndCanceling(inspector, view, testActor);
 });
 
-addTest("When all properties are set on the node editing one should work",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
+function* testEditing(inspector, view, testActor) {
+  info("When all properties are set on the node editing one should work");
 
-  node.style.padding = "5px";
+  yield setStyle(testActor, "#div1", "padding", "5px");
   yield waitForUpdate(inspector);
 
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-padding.layout-bottom > span");
   is(span.textContent, 5, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "5px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("7", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "7", "Should have the right value in the editor.");
-  is(getStyle(node, "padding-bottom"), "7px", "Should have updated the padding");
+  is((yield getStyle(testActor, "#div1", "padding-bottom")), "7px",
+     "Should have updated the padding");
 
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "padding-bottom"), "7px", "Should be the right padding.")
+  is((yield getStyle(testActor, "#div1", "padding-bottom")), "7px",
+     "Should be the right padding.");
   is(span.textContent, 7, "Should have the right value in the box model.");
-});
+}
 
-addTest("When all properties are set on the node editing one should work",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
+function* testEditingAndCanceling(inspector, view, testActor) {
+  info("When all properties are set on the node editing one and then " +
+       "cancelling with ESCAPE should work");
 
-  node.style.padding = "5px";
+  yield setStyle(testActor, "#div1", "padding", "5px");
   yield waitForUpdate(inspector);
 
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-padding.layout-left > span");
   is(span.textContent, 5, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "5px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("8", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "8", "Should have the right value in the editor.");
-  is(getStyle(node, "padding-left"), "8px", "Should have updated the padding");
+  is((yield getStyle(testActor, "#div1", "padding-left")), "8px",
+     "Should have updated the padding");
 
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "padding-left"), "5px", "Should be the right padding.")
+  is((yield getStyle(testActor, "#div1", "padding-left")), "5px",
+     "Should be the right padding.");
   is(span.textContent, 5, "Should have the right value in the box model.");
-});
+}
 
-addTest("When all properties are set on the node deleting one should work",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
-
-  node.style.padding = "5px";
+function* testDeleting(inspector, view, testActor) {
+  info("When all properties are set on the node deleting one should work");
 
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-padding.layout-left > span");
   is(span.textContent, 5, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "5px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "", "Should have the right value in the editor.");
-  is(getStyle(node, "padding-left"), "", "Should have updated the padding");
+  is((yield getStyle(testActor, "#div1", "padding-left")), "",
+     "Should have updated the padding");
 
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "padding-left"), "", "Should be the right padding.")
+  is((yield getStyle(testActor, "#div1", "padding-left")), "",
+     "Should be the right padding.");
   is(span.textContent, 3, "Should have the right value in the box model.");
-});
+}
 
-addTest("When all properties are set on the node deleting one then cancelling should work",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
+function* testDeletingAndCanceling(inspector, view, testActor) {
+  info("When all properties are set on the node deleting one then cancelling " +
+       "should work");
 
-  node.style.padding = "5px";
+  yield setStyle(testActor, "#div1", "padding", "5px");
   yield waitForUpdate(inspector);
 
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-padding.layout-left > span");
   is(span.textContent, 5, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "5px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("VK_DELETE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "", "Should have the right value in the editor.");
-  is(getStyle(node, "padding-left"), "", "Should have updated the padding");
+  is((yield getStyle(testActor, "#div1", "padding-left")), "",
+     "Should have updated the padding");
 
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "padding-left"), "5px", "Should be the right padding.")
+  is((yield getStyle(testActor, "#div1", "padding-left")), "5px",
+     "Should be the right padding.");
   is(span.textContent, 5, "Should have the right value in the box model.");
-});
+}
--- a/devtools/client/inspector/layout/test/browser_layout_editablemodel_border.js
+++ b/devtools/client/inspector/layout/test/browser_layout_editablemodel_border.js
@@ -9,43 +9,44 @@
 const TEST_URI = "<style>" +
   "div { margin: 10px; padding: 3px }" +
   "#div1 { margin-top: 5px }" +
   "#div2 { border-bottom: 1em solid black; }" +
   "#div3 { padding: 2em; }" +
   "</style>" +
   "<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
 
-function getStyle(node, property) {
-  return node.style.getPropertyValue(property);
-}
-
 add_task(function*() {
   yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view, testActor} = yield openLayoutView();
 
-  let node = content.document.getElementById("div1");
-  is(getStyle(node, "border-top-width"), "", "Should have the right border");
-  is(getStyle(node, "border-top-style"), "", "Should have the right border");
+  is((yield getStyle(testActor, "#div1", "border-top-width")), "",
+     "Should have the right border");
+  is((yield getStyle(testActor, "#div1", "border-top-style")), "",
+     "Should have the right border");
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-border.layout-top > span");
   is(span.textContent, 0, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "0", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("1", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "1", "Should have the right value in the editor.");
-  is(getStyle(node, "border-top-width"), "1px", "Should have the right border");
-  is(getStyle(node, "border-top-style"), "solid", "Should have the right border");
+  is((yield getStyle(testActor, "#div1", "border-top-width")), "1px",
+     "Should have the right border");
+  is((yield getStyle(testActor, "#div1", "border-top-style")), "solid",
+     "Should have the right border");
 
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "border-top-width"), "", "Should be the right padding.")
-  is(getStyle(node, "border-top-style"), "", "Should have the right border");
+  is((yield getStyle(testActor, "#div1", "border-top-width")), "",
+     "Should be the right padding.");
+  is((yield getStyle(testActor, "#div1", "border-top-style")), "",
+     "Should have the right border");
   is(span.textContent, 0, "Should have the right value in the box model.");
 });
--- a/devtools/client/inspector/layout/test/browser_layout_editablemodel_stylerules.js
+++ b/devtools/client/inspector/layout/test/browser_layout_editablemodel_stylerules.js
@@ -10,97 +10,104 @@
 const TEST_URI = "<style>" +
   "div { margin: 10px; padding: 3px }" +
   "#div1 { margin-top: 5px }" +
   "#div2 { border-bottom: 1em solid black; }" +
   "#div3 { padding: 2em; }" +
   "</style>" +
   "<div id='div1'></div><div id='div2'></div><div id='div3'></div>";
 
-function getStyle(node, property) {
-  return node.style.getPropertyValue(property);
-}
-
 add_task(function*() {
   yield addTab("data:text/html," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view, testActor} = yield openLayoutView();
 
-  yield runTests(inspector, view);
+  yield testUnits(inspector, view, testActor);
+  yield testValueComesFromStyleRule(inspector, view, testActor);
+  yield testShorthandsAreParsed(inspector, view, testActor);
 });
 
-addTest("Test that entering units works",
-function*(inspector, view) {
-  let node = content.document.getElementById("div1");
-  is(getStyle(node, "padding-top"), "", "Should have the right padding");
+function* testUnits(inspector, view, testActor) {
+  info("Test that entering units works");
+
+  is((yield getStyle(testActor, "#div1", "padding-top")), "",
+     "Should have the right padding");
   yield selectNode("#div1", inspector);
 
   let span = view.doc.querySelector(".layout-padding.layout-top > span");
   is(span.textContent, 3, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "3px", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("1", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
   EventUtils.synthesizeKey("e", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
-  is(getStyle(node, "padding-top"), "", "An invalid value is handled cleanly");
+  is((yield getStyle(testActor, "#div1", "padding-top")), "",
+     "An invalid value is handled cleanly");
 
   EventUtils.synthesizeKey("m", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "1em", "Should have the right value in the editor.");
-  is(getStyle(node, "padding-top"), "1em", "Should have updated the padding.");
+  is((yield getStyle(testActor, "#div1", "padding-top")),
+     "1em", "Should have updated the padding.");
 
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "padding-top"), "1em", "Should be the right padding.")
+  is((yield getStyle(testActor, "#div1", "padding-top")), "1em",
+     "Should be the right padding.");
   is(span.textContent, 16, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that we pick up the value from a higher style rule",
-function*(inspector, view) {
-  let node = content.document.getElementById("div2");
-  is(getStyle(node, "border-bottom-width"), "", "Should have the right border-bottom-width");
+function* testValueComesFromStyleRule(inspector, view, testActor) {
+  info("Test that we pick up the value from a higher style rule");
+
+  is((yield getStyle(testActor, "#div2", "border-bottom-width")), "",
+     "Should have the right border-bottom-width");
   yield selectNode("#div2", inspector);
 
   let span = view.doc.querySelector(".layout-border.layout-bottom > span");
   is(span.textContent, 16, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "1em", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("0", {}, view.doc.defaultView);
   yield waitForUpdate(inspector);
 
   is(editor.value, "0", "Should have the right value in the editor.");
-  is(getStyle(node, "border-bottom-width"), "0px", "Should have updated the border.");
+  is((yield getStyle(testActor, "#div2", "border-bottom-width")), "0px",
+     "Should have updated the border.");
 
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "border-bottom-width"), "0px", "Should be the right border-bottom-width.")
+  is((yield getStyle(testActor, "#div2", "border-bottom-width")), "0px",
+     "Should be the right border-bottom-width.");
   is(span.textContent, 0, "Should have the right value in the box model.");
-});
+}
 
-addTest("Test that shorthand properties are parsed correctly",
-function*(inspector, view) {
-  let node = content.document.getElementById("div3");
-  is(getStyle(node, "padding-right"), "", "Should have the right padding");
+function* testShorthandsAreParsed(inspector, view, testActor) {
+  info("Test that shorthand properties are parsed correctly");
+
+  is((yield getStyle(testActor, "#div3", "padding-right")), "",
+     "Should have the right padding");
   yield selectNode("#div3", inspector);
 
   let span = view.doc.querySelector(".layout-padding.layout-right > span");
   is(span.textContent, 32, "Should have the right value in the box model.");
 
   EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
   let editor = view.doc.querySelector(".styleinspector-propertyeditor");
   ok(editor, "Should have opened the editor.");
   is(editor.value, "2em", "Should have the right value in the editor.");
 
   EventUtils.synthesizeKey("VK_RETURN", {}, view.doc.defaultView);
 
-  is(getStyle(node, "padding-right"), "", "Should be the right padding.")
+  is((yield getStyle(testActor, "#div3", "padding-right")), "",
+     "Should be the right padding.");
   is(span.textContent, 32, "Should have the right value in the box model.");
-});
+}
--- a/devtools/client/inspector/layout/test/browser_layout_guides.js
+++ b/devtools/client/inspector/layout/test/browser_layout_guides.js
@@ -4,50 +4,51 @@
 "use strict";
 
 // Test that hovering over regions in the box-model shows the highlighter with
 // the right options.
 // Tests that actually check the highlighter is displayed and correct are in the
 // devtools/inspector/test folder. This test only cares about checking that the
 // layout-view does call the highlighter, and it does so by mocking it.
 
-const STYLE = "div { position: absolute; top: 50px; left: 50px; height: 10px; " +
-              "width: 10px; border: 10px solid black; padding: 10px; margin: 10px;}";
+const STYLE = "div { position: absolute; top: 50px; left: 50px; " +
+              "height: 10px; width: 10px; border: 10px solid black; " +
+              "padding: 10px; margin: 10px;}";
 const HTML = "<style>" + STYLE + "</style><div></div>";
 const TEST_URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
 
 var highlightedNodeFront, highlighterOptions;
 
 add_task(function*() {
   yield addTab(TEST_URL);
   let {toolbox, inspector, view} = yield openLayoutView();
   yield selectNode("div", inspector);
 
   // Mock the highlighter by replacing the showBoxModel method.
   toolbox.highlighter.showBoxModel = function(nodeFront, options) {
     highlightedNodeFront = nodeFront;
     highlighterOptions = options;
-  }
+  };
 
   let elt = view.doc.getElementById("layout-margins");
   yield testGuideOnLayoutHover(elt, "margin", inspector, view);
 
   elt = view.doc.getElementById("layout-borders");
   yield testGuideOnLayoutHover(elt, "border", inspector, view);
 
   elt = view.doc.getElementById("layout-padding");
   yield testGuideOnLayoutHover(elt, "padding", inspector, view);
 
   elt = view.doc.getElementById("layout-content");
   yield testGuideOnLayoutHover(elt, "content", inspector, view);
 });
 
-function* testGuideOnLayoutHover(elt, expectedRegion, inspector, view) {
+function* testGuideOnLayoutHover(elt, expectedRegion, inspector) {
   info("Synthesizing mouseover on the layout-view");
-  EventUtils.synthesizeMouse(elt, 2, 2, {type:'mouseover'},
+  EventUtils.synthesizeMouse(elt, 2, 2, {type: "mouseover"},
     elt.ownerDocument.defaultView);
 
   info("Waiting for the node-highlight event from the toolbox");
   yield inspector.toolbox.once("node-highlight");
 
   is(highlightedNodeFront, inspector.selection.nodeFront,
     "The right nodeFront was highlighted");
   is(highlighterOptions.region, expectedRegion,
--- a/devtools/client/inspector/layout/test/browser_layout_rotate-labels-on-sides.js
+++ b/devtools/client/inspector/layout/test/browser_layout_rotate-labels-on-sides.js
@@ -2,41 +2,42 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that longer values are rotated on the side
 
 const res1 = [
-  {selector: ".layout-margin.layout-top > span",         value: 30},
-  {selector: ".layout-margin.layout-left > span",        value: "auto"},
-  {selector: ".layout-margin.layout-bottom > span",      value: 30},
-  {selector: ".layout-margin.layout-right > span",       value: "auto"},
-  {selector: ".layout-padding.layout-top > span",        value: 20},
-  {selector: ".layout-padding.layout-left > span",       value: 2000000},
-  {selector: ".layout-padding.layout-bottom > span",     value: 20},
-  {selector: ".layout-padding.layout-right > span",      value: 20},
-  {selector: ".layout-border.layout-top > span",         value: 10},
-  {selector: ".layout-border.layout-left > span",        value: 10},
-  {selector: ".layout-border.layout-bottom > span",      value: 10},
-  {selector: ".layout-border.layout-right > span",       value: 10},
+  {selector: ".layout-margin.layout-top > span", value: 30},
+  {selector: ".layout-margin.layout-left > span", value: "auto"},
+  {selector: ".layout-margin.layout-bottom > span", value: 30},
+  {selector: ".layout-margin.layout-right > span", value: "auto"},
+  {selector: ".layout-padding.layout-top > span", value: 20},
+  {selector: ".layout-padding.layout-left > span", value: 2000000},
+  {selector: ".layout-padding.layout-bottom > span", value: 20},
+  {selector: ".layout-padding.layout-right > span", value: 20},
+  {selector: ".layout-border.layout-top > span", value: 10},
+  {selector: ".layout-border.layout-left > span", value: 10},
+  {selector: ".layout-border.layout-bottom > span", value: 10},
+  {selector: ".layout-border.layout-right > span", value: 10},
 ];
 
 const TEST_URI = encodeURIComponent([
   "<style>",
-  "div{border:10px solid black; padding: 20px 20px 20px 2000000px; margin: 30px auto;}",
+  "div { border:10px solid black; padding: 20px 20px 20px 2000000px; " +
+  "margin: 30px auto; }",
   "</style>",
   "<div></div>"
 ].join(""));
 const LONG_TEXT_ROTATE_LIMIT = 3;
 
 add_task(function*() {
   yield addTab("data:text/html," + TEST_URI);
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view} = yield openLayoutView();
   yield selectNode("div", inspector);
 
   for (let i = 0; i < res1.length; i++) {
     let elt = view.doc.querySelector(res1[i].selector);
     let isLong = elt.textContent.length > LONG_TEXT_ROTATE_LIMIT;
     let classList = elt.parentNode.classList;
     let canBeRotated = classList.contains("layout-left") ||
                        classList.contains("layout-right");
--- a/devtools/client/inspector/layout/test/browser_layout_tooltips.js
+++ b/devtools/client/inspector/layout/test/browser_layout_tooltips.js
@@ -37,24 +37,24 @@ const VALUES_TEST_DATA = [{
     name: "margin-bottom",
     ruleSelector: "#div1",
     styleSheetLocation: "null:1"
   }, {
     name: "margin-left",
     ruleSelector: "#div1",
     styleSheetLocation: "null:1"
   }]
-},{
+}, {
   selector: "#div2",
   values: [{
     name: "border-bottom-width",
     ruleSelector: "#div2",
     styleSheetLocation: "null:2"
   }]
-},{
+}, {
   selector: "#div3",
   values: [{
     name: "padding-top",
     ruleSelector: "html, body, #div3",
     styleSheetLocation: "null:3"
   }, {
     name: "padding-right",
     ruleSelector: "html, body, #div3",
@@ -67,17 +67,17 @@ const VALUES_TEST_DATA = [{
     name: "padding-left",
     ruleSelector: "html, body, #div3",
     styleSheetLocation: "null:3"
   }]
 }];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view} = yield openLayoutView();
 
   info("Checking the regions tooltips");
 
   ok(view.doc.querySelector("#layout-margins").hasAttribute("title"),
     "The margin region has a tooltip");
   is(view.doc.querySelector("#layout-margins").getAttribute("title"), "margin",
     "The margin region has the correct tooltip content");
 
--- a/devtools/client/inspector/layout/test/browser_layout_update-after-navigation.js
+++ b/devtools/client/inspector/layout/test/browser_layout_update-after-navigation.js
@@ -2,97 +2,90 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the layout-view continues to work after a page navigation and that
 // it also works after going back
 
+const IFRAME1 = URL_ROOT + "doc_layout_iframe1.html";
+const IFRAME2 = URL_ROOT + "doc_layout_iframe2.html";
+
 add_task(function*() {
-  yield addTab(URL_ROOT + "doc_layout_iframe1.html");
-  let {toolbox, inspector, view} = yield openLayoutView();
-  yield runTests(inspector, view);
+  yield addTab(IFRAME1);
+  let {inspector, view, testActor} = yield openLayoutView();
+
+  yield testFirstPage(inspector, view, testActor);
+
+  info("Navigate to the second page");
+  yield testActor.eval(`content.location.href="${IFRAME2}"`);
+  yield inspector.once("markuploaded");
+
+  yield testSecondPage(inspector, view, testActor);
+
+  info("Go back to the first page");
+  yield testActor.eval("content.history.back();");
+  yield inspector.once("markuploaded");
+
+  yield testBackToFirstPage(inspector, view, testActor);
 });
 
-addTest("Test that the layout-view works on the first page",
-function*(inspector, view) {
+function* testFirstPage(inspector, view, testActor) {
+  info("Test that the layout-view works on the first page");
+
   info("Selecting the test node");
   yield selectNode("p", inspector);
 
   info("Checking that the layout-view shows the right value");
   let paddingElt = view.doc.querySelector(".layout-padding.layout-top > span");
   is(paddingElt.textContent, "50");
 
   info("Listening for layout-view changes and modifying the padding");
   let onUpdated = waitForUpdate(inspector);
-  getNode("p").style.padding = "20px";
+  yield setStyle(testActor, "p", "padding", "20px");
   yield onUpdated;
   ok(true, "Layout-view got updated");
 
   info("Checking that the layout-view shows the right value after update");
   is(paddingElt.textContent, "20");
-});
+}
 
-addTest("Navigate to the second page",
-function*(inspector, view) {
-  yield navigateTo(URL_ROOT + "doc_layout_iframe2.html");
-  yield inspector.once("markuploaded");
-});
+function* testSecondPage(inspector, view, testActor) {
+  info("Test that the layout-view works on the second page");
 
-addTest("Test that the layout-view works on the second page",
-function*(inspector, view) {
   info("Selecting the test node");
   yield selectNode("p", inspector);
 
   info("Checking that the layout-view shows the right value");
   let sizeElt = view.doc.querySelector(".layout-size > span");
   is(sizeElt.textContent, "100" + "\u00D7" + "100");
 
   info("Listening for layout-view changes and modifying the size");
   let onUpdated = waitForUpdate(inspector);
-  getNode("p").style.width = "200px";
+  yield setStyle(testActor, "p", "width", "200px");
   yield onUpdated;
   ok(true, "Layout-view got updated");
 
   info("Checking that the layout-view shows the right value after update");
   is(sizeElt.textContent, "200" + "\u00D7" + "100");
-});
+}
 
-addTest("Go back to the first page",
-function*(inspector, view) {
-  content.history.back();
-  yield inspector.once("markuploaded");
-});
+function* testBackToFirstPage(inspector, view, testActor) {
+  info("Test that the layout-view works on the first page after going back");
 
-addTest("Test that the layout-view works on the first page after going back",
-function*(inspector, view) {
   info("Selecting the test node");
   yield selectNode("p", inspector);
 
   info("Checking that the layout-view shows the right value, which is the" +
     "modified value from step one because of the bfcache");
   let paddingElt = view.doc.querySelector(".layout-padding.layout-top > span");
   is(paddingElt.textContent, "20");
 
   info("Listening for layout-view changes and modifying the padding");
   let onUpdated = waitForUpdate(inspector);
-  getNode("p").style.padding = "100px";
+  yield setStyle(testActor, "p", "padding", "100px");
   yield onUpdated;
   ok(true, "Layout-view got updated");
 
   info("Checking that the layout-view shows the right value after update");
   is(paddingElt.textContent, "100");
-});
-
-function navigateTo(url) {
-  info("Navigating to " + url);
-
-  let def = promise.defer();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    info("URL " + url + " loading complete");
-    waitForFocus(def.resolve, content);
-  }, true);
-  content.location = url;
-
-  return def.promise;
 }
--- a/devtools/client/inspector/layout/test/browser_layout_update-after-reload.js
+++ b/devtools/client/inspector/layout/test/browser_layout_update-after-reload.js
@@ -3,38 +3,38 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the layout-view continues to work after the page is reloaded
 
 add_task(function*() {
   yield addTab(URL_ROOT + "doc_layout_iframe1.html");
-  let {toolbox, inspector, view} = yield openLayoutView();
+  let {inspector, view, testActor} = yield openLayoutView();
 
   info("Test that the layout-view works on the first page");
-  yield assertLayoutView(inspector, view);
+  yield assertLayoutView(inspector, view, testActor);
 
   info("Reload the page");
-  content.location.reload();
+  yield testActor.eval("content.location.reload();");
   yield inspector.once("markuploaded");
 
   info("Test that the layout-view works on the reloaded page");
-  yield assertLayoutView(inspector, view);
+  yield assertLayoutView(inspector, view, testActor);
 });
 
-function* assertLayoutView(inspector, view) {
+function* assertLayoutView(inspector, view, testActor) {
   info("Selecting the test node");
   yield selectNode("p", inspector);
 
   info("Checking that the layout-view shows the right value");
   let paddingElt = view.doc.querySelector(".layout-padding.layout-top > span");
   is(paddingElt.textContent, "50");
 
   info("Listening for layout-view changes and modifying the padding");
   let onUpdated = waitForUpdate(inspector);
-  getNode("p").style.padding = "20px";
+  yield setStyle(testActor, "p", "padding", "20px");
   yield onUpdated;
   ok(true, "Layout-view got updated");
 
   info("Checking that the layout-view shows the right value after update");
   is(paddingElt.textContent, "20");
 }
--- a/devtools/client/inspector/layout/test/browser_layout_update-in-iframes.js
+++ b/devtools/client/inspector/layout/test/browser_layout_update-in-iframes.js
@@ -4,70 +4,98 @@
 
 "use strict";
 
 // Test that the layout-view for elements within iframes also updates when they
 // change
 
 add_task(function*() {
   yield addTab(URL_ROOT + "doc_layout_iframe1.html");
-  let iframe2 = getNode("iframe").contentDocument.querySelector("iframe");
+  let {inspector, view, testActor} = yield openLayoutView();
 
-  let {toolbox, inspector, view} = yield openLayoutView();
-  yield runTests(inspector, view, iframe2);
+  yield testResizingInIframe(inspector, view, testActor);
+  yield testReflowsAfterIframeDeletion(inspector, view, testActor);
 });
 
-addTest("Test that resizing an element in an iframe updates its box model",
-function*(inspector, view, iframe2) {
+function* testResizingInIframe(inspector, view, testActor) {
+  info("Test that resizing an element in an iframe updates its box model");
+
   info("Selecting the nested test node");
-  let node = iframe2.contentDocument.querySelector("div");
   yield selectNodeInIframe2("div", inspector);
 
   info("Checking that the layout-view shows the right value");
   let sizeElt = view.doc.querySelector(".layout-size > span");
   is(sizeElt.textContent, "400\u00D7200");
 
   info("Listening for layout-view changes and modifying its size");
   let onUpdated = waitForUpdate(inspector);
-  node.style.width = "200px";
+  yield setStyleInIframe2(testActor, "div", "width", "200px");
   yield onUpdated;
   ok(true, "Layout-view got updated");
 
   info("Checking that the layout-view shows the right value after update");
   is(sizeElt.textContent, "200\u00D7200");
-});
+}
 
-addTest("Test reflows are still sent to the layout-view after deleting an iframe",
-function*(inspector, view, iframe2) {
+function* testReflowsAfterIframeDeletion(inspector, view, testActor) {
+  info("Test reflows are still sent to the layout-view after deleting an " +
+       "iframe");
+
   info("Deleting the iframe2");
-  iframe2.remove();
+  yield removeIframe2(testActor);
   yield inspector.once("inspector-updated");
 
   info("Selecting the test node in iframe1");
-  let node = getNode("iframe").contentDocument.querySelector("p");
   yield selectNodeInIframe1("p", inspector);
 
   info("Checking that the layout-view shows the right value");
   let sizeElt = view.doc.querySelector(".layout-size > span");
   is(sizeElt.textContent, "100\u00D7100");
 
   info("Listening for layout-view changes and modifying its size");
   let onUpdated = waitForUpdate(inspector);
-  node.style.width = "200px";
+  yield setStyleInIframe1(testActor, "p", "width", "200px");
   yield onUpdated;
   ok(true, "Layout-view got updated");
 
   info("Checking that the layout-view shows the right value after update");
   is(sizeElt.textContent, "200\u00D7100");
-});
+}
 
 function* selectNodeInIframe1(selector, inspector) {
   let iframe1 = yield getNodeFront("iframe", inspector);
   let node = yield getNodeFrontInFrame(selector, iframe1, inspector);
   yield selectNode(node, inspector);
 }
 
 function* selectNodeInIframe2(selector, inspector) {
   let iframe1 = yield getNodeFront("iframe", inspector);
   let iframe2 = yield getNodeFrontInFrame("iframe", iframe1, inspector);
   let node = yield getNodeFrontInFrame(selector, iframe2, inspector);
   yield selectNode(node, inspector);
 }
+
+function* setStyleInIframe1(testActor, selector, propertyName, value) {
+  yield testActor.eval(`
+    content.document.querySelector("iframe")
+           .contentDocument.querySelector("${selector}")
+           .style.${propertyName} = "${value}";
+  `);
+}
+
+function* setStyleInIframe2(testActor, selector, propertyName, value) {
+  yield testActor.eval(`
+    content.document.querySelector("iframe")
+           .contentDocument
+           .querySelector("iframe")
+           .contentDocument.querySelector("${selector}")
+           .style.${propertyName} = "${value}";
+  `);
+}
+
+function* removeIframe2(testActor) {
+  yield testActor.eval(`
+    content.document.querySelector("iframe")
+           .contentDocument
+           .querySelector("iframe")
+           .remove();
+  `);
+}
--- a/devtools/client/inspector/layout/test/head.js
+++ b/devtools/client/inspector/layout/test/head.js
@@ -1,11 +1,14 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../../framework/test/shared-head.js */
+/* import-globals-from ../../test/head.js */
 "use strict";
 
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
 Services.prefs.setIntPref("devtools.toolbox.footer.height", 350);
@@ -47,61 +50,51 @@ function selectAndHighlightNode(nodeOrSe
 
 /**
  * Open the toolbox, with the inspector tool visible, and the layout-view
  * sidebar tab selected.
  * @return a promise that resolves when the inspector is ready and the layout
  * view is visible and ready
  */
 function openLayoutView() {
-  return openInspectorSidebarTab("layoutview").then(({toolbox, inspector}) => {
+  return openInspectorSidebarTab("layoutview").then(data => {
     // The actual highligher show/hide methods are mocked in layoutview tests.
     // The highlighter is tested in devtools/inspector/test.
     function mockHighlighter({highlighter}) {
-      highlighter.showBoxModel = function(nodeFront, options) {
+      highlighter.showBoxModel = function() {
         return promise.resolve();
       };
       highlighter.hideBoxModel = function() {
         return promise.resolve();
       };
     }
-    mockHighlighter(toolbox);
+    mockHighlighter(data.toolbox);
 
     return {
-      toolbox,
-      inspector,
-      view: inspector.layoutview
+      toolbox: data.toolbox,
+      inspector: data.inspector,
+      view: data.inspector.layoutview,
+      testActor: data.testActor
     };
   });
 }
 
 /**
  * Wait for the layoutview-updated event.
  * @return a promise
  */
 function waitForUpdate(inspector) {
   return inspector.once("layoutview-updated");
 }
 
-/**
- * The addTest/runTests function couple provides a simple way to define
- * subsequent test cases in a test file. Example:
- *
- * addTest("what this test does", function*() {
- *   ... actual code for the test ...
- * });
- * addTest("what this second test does", function*() {
- *   ... actual code for the second test ...
- * });
- * runTests().then(...);
- */
-var TESTS = [];
-
-function addTest(message, func) {
-  TESTS.push([message, Task.async(func)]);
+function getStyle(testActor, selector, propertyName) {
+  return testActor.eval(`
+    content.document.querySelector("${selector}")
+                    .style.getPropertyValue("${propertyName}");
+  `);
 }
 
-var runTests = Task.async(function*(...args) {
-  for (let [message, test] of TESTS) {
-    info("Running new test case: " + message);
-    yield test.apply(null, args);
-  }
-});
+function setStyle(testActor, selector, propertyName, value) {
+  return testActor.eval(`
+    content.document.querySelector("${selector}")
+                    .style.${propertyName} = "${value}";
+  `);
+}
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -165,24 +165,25 @@ function getActiveInspector() {
  * Open the toolbox, with the inspector tool visible, and the one of the sidebar
  * tabs selected.
  * @param {String} id The ID of the sidebar tab to be opened
  * @param {String} hostType Optional hostType, as defined in Toolbox.HostType
  * @return a promise that resolves when the inspector is ready and the tab is
  * visible and ready
  */
 var openInspectorSidebarTab = Task.async(function*(id, hostType) {
-  let {toolbox, inspector} = yield openInspector();
+  let {toolbox, inspector, testActor} = yield openInspector();
 
   info("Selecting the " + id + " sidebar");
   inspector.sidebar.select(id);
 
   return {
     toolbox,
-    inspector
+    inspector,
+    testActor
   };
 });
 
 /**
  * Get the NodeFront for a node that matches a given css selector, via the
  * protocol.
  * @param {String|NodeFront} selector
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -56,24 +56,45 @@ player.infiniteIterationCountText=∞
 # This string is displayed in each animation player widget, to indicate either
 # how long (in seconds) the animation lasts, or what is the animation's current
 # time (in seconds too);
 player.timeLabel=%Ss
 
 # LOCALIZATION NOTE (player.playbackRateLabel):
 # This string is displayed in each animation player widget, as the label of
 # drop-down list items that can be used to change the rate at which the
-# animation runs (1x being the default, 2x being twice as fast).
-player.playbackRateLabel=%Sx
+# animation runs (1× being the default, 2× being twice as fast).
+player.playbackRateLabel=%S×
 
 # LOCALIZATION NOTE (player.runningOnCompositorTooltip):
 # This string is displayed as a tooltip for the icon that indicates that the
 # animation is running on the compositor thread.
 player.runningOnCompositorTooltip=This animation is running on compositor thread
 
+# LOCALIZATION NOTE (timeline.rateSelectorTooltip):
+# This string is displayed in the timeline toolbar, as the tooltip of the
+# drop-down list that can be used to change the rate at which the animations
+# run.
+timeline.rateSelectorTooltip=Set the animations playback rates
+
+# LOCALIZATION NOTE (timeline.pauseResumeButtonTooltip):
+# This string is displayed in the timeline toolbar, as the tooltip of the
+# pause/resume button that can be used to pause or resume the animations
+timeline.pausedButtonTooltip=Resume the animations
+
+# LOCALIZATION NOTE (timeline.pauseResumeButtonTooltip):
+# This string is displayed in the timeline toolbar, as the tooltip of the
+# pause/resume button that can be used to pause or resume the animations
+timeline.resumedButtonTooltip=Pause the animations
+
+# LOCALIZATION NOTE (timeline.rewindButtonTooltip):
+# This string is displayed in the timeline toolbar, as the tooltip of the
+# rewind button that can be used to rewind the animations
+timeline.rewindButtonTooltip=Rewind the animations
+
 # LOCALIZATION NOTE (timeline.timeGraduationLabel):
 # This string is displayed at the top of the animation panel, next to each time
 # graduation, to indicate what duration (in milliseconds) this graduation
 # corresponds to.
 timeline.timeGraduationLabel=%Sms
 
 # LOCALIZATION NOTE (timeline.cssanimation.nameLabel):
 # This string is displayed in a tooltip of the animation panel that is shown
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -4,18 +4,18 @@
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://testing-common/Assert.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
-var { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
-var { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
 DevToolsUtils.testing = true;
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -173,21 +173,25 @@ body {
   font-family: inherit;
   color: var(--theme-body-color);
   font-size: 1em;
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
+  background-image: url("chrome://devtools/skin/images/dropmarker.svg");
+  background-repeat: no-repeat;
+  background-position: calc(100% - 4px) center;
+  padding-right: 1em;
 }
 
 #timeline-rate {
   position: relative;
-  width: 4em;
+  width: 4.5em;
 }
 
 /* Animation timeline component */
 
 .animation-timeline {
   height: 100%;
   overflow: hidden;
   position: relative;
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -1153,13 +1153,13 @@ var Cmds = {
 
   resetZoom: function() {
     UI.contentViewer.fullZoom = 1;
     Services.prefs.setCharPref("devtools.webide.zoom", 1);
   },
 
   reloadDevtools: function(event) {
     if (Services.prefs.prefHasUserValue("devtools.loader.srcdir")) {
-      let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+      let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
       devtools.reload();
     }
   }
 };
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -36,16 +36,17 @@ const {NodeActor} = require("devtools/se
 const events = require("sdk/event/core");
 
 // Types of animations.
 const ANIMATION_TYPES = {
   CSS_ANIMATION: "cssanimation",
   CSS_TRANSITION: "csstransition",
   UNKNOWN: "unknown"
 };
+exports.ANIMATION_TYPES = ANIMATION_TYPES;
 
 /**
  * The AnimationPlayerActor provides information about a given animation: its
  * startTime, currentTime, current state, etc.
  *
  * Since the state of a player changes as the animation progresses it is often
  * useful to call getCurrentState at regular intervals to get the current state.
  *
@@ -66,41 +67,40 @@ var AnimationPlayerActor = ActorClass({
    * @param {AnimationPlayer} The player object returned by getAnimationPlayers
    */
   initialize: function(animationsActor, player) {
     Actor.prototype.initialize.call(this, animationsActor.conn);
 
     this.onAnimationMutation = this.onAnimationMutation.bind(this);
 
     this.walker = animationsActor.walker;
-    this.tabActor = animationsActor.tabActor;
     this.player = player;
     this.node = player.effect.target;
 
-    let win = this.node.ownerDocument.defaultView;
-    this.styles = win.getComputedStyle(this.node);
-
     // Listen to animation mutations on the node to alert the front when the
     // current animation changes.
-    this.observer = new win.MutationObserver(this.onAnimationMutation);
+    this.observer = new this.window.MutationObserver(this.onAnimationMutation);
     this.observer.observe(this.node, {animations: true});
   },
 
   destroy: function() {
     // Only try to disconnect the observer if it's not already dead (i.e. if the
     // container view hasn't navigated since).
     if (this.observer && !Cu.isDeadWrapper(this.observer)) {
       this.observer.disconnect();
     }
-    this.tabActor = this.player = this.node = this.styles = null;
-    this.observer = this.walker = null;
+    this.player = this.node = this.observer = this.walker = null;
 
     Actor.prototype.destroy.call(this);
   },
 
+  get window() {
+    return this.node.ownerDocument.defaultView;
+  },
+
   /**
    * Release the actor, when it isn't needed anymore.
    * Protocol.js uses this release method to call the destroy method.
    */
   release: method(function() {}, {release: true}),
 
   form: function(detail) {
     if (detail === "actorid") {
@@ -114,22 +114,22 @@ var AnimationPlayerActor = ActorClass({
     // return its corresponding NodeActor ID too.
     if (this.walker && this.walker.hasNode(this.node)) {
       data.animationTargetNodeActorID = this.walker.getNode(this.node).actorID;
     }
 
     return data;
   },
 
-  isAnimation: function(player=this.player) {
-    return player instanceof this.tabActor.window.CSSAnimation;
+  isAnimation: function(player = this.player) {
+    return player instanceof this.window.CSSAnimation;
   },
 
-  isTransition: function(player=this.player) {
-    return player instanceof this.tabActor.window.CSSTransition;
+  isTransition: function(player = this.player) {
+    return player instanceof this.window.CSSTransition;
   },
 
   getType: function() {
     if (this.isAnimation()) {
       return ANIMATION_TYPES.CSS_ANIMATION;
     } else if (this.isTransition()) {
       return ANIMATION_TYPES.CSS_TRANSITION;
     }
@@ -367,16 +367,18 @@ var AnimationPlayerActor = ActorClass({
   }, {
     request: {},
     response: {
       frames: RetVal("json")
     }
   })
 });
 
+exports.AnimationPlayerActor = AnimationPlayerActor;
+
 var AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
   initialize: function(conn, form, detail, ctx) {
     Front.prototype.initialize.call(this, conn, form, detail, ctx);
 
     this.state = {};
   },
 
   form: function(form, detail) {
--- a/devtools/server/tests/browser/animation.html
+++ b/devtools/server/tests/browser/animation.html
@@ -140,56 +140,28 @@
     }
   }
 
   @keyframes grow {
     100% {
       width: 100px;
     }
   }
-
-  .script-generated {
-    display: inline-block;
-
-    width: 50px;
-    height: 50px;
-    border-radius: 50%;
-    background-color: black;
-    background-image:
-      repeating-linear-gradient(45deg, transparent 0, transparent 5px, #f06 5px, #f06 10px);
-    border: 5px solid #f06;
-    box-sizing: border-box;
-  }
 </style>
 <div class="not-animated"></div>
 <div class="simple-animation"></div>
 <div class="multiple-animations"></div>
 <div class="transition"></div>
 <div class="long-animation"></div>
 <div class="short-animation"></div>
 <div class="delayed-animation"></div>
 <div class="delayed-transition"></div>
 <div class="delayed-multiple-animations"></div>
 <div class="multiple-animations-2"></div>
 <div class="all-transitions"></div>
-<div class="script-generated"></div>
 <script type="text/javascript">
   // Get the transitions started when the page loads
   var players;
   addEventListener("load", function() {
     document.querySelector(".transition").classList.add("get-round");
     document.querySelector(".delayed-transition").classList.add("get-round");
-
-    // Create a script-generated animation.
-    var animation = document.querySelector(".script-generated").animate({
-      backgroundColor: ["black", "gold"]
-    }, {
-      duration: 500,
-      iterations: Infinity,
-      direction: "alternate"
-    });
-    animation.id = "custom-animation-name";
-
-    // Add a custom animation id to an existing css animation.
-    document.querySelector(".delayed-animation")
-            .getAnimations()[0].id = "cssanimation-custom-name";
   });
 </script>
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -23,17 +23,16 @@ support-files =
 
 [browser_animation_emitMutations.js]
 [browser_animation_getFrames.js]
 [browser_animation_getMultipleStates.js]
 [browser_animation_getPlayers.js]
 [browser_animation_getStateAfterFinished.js]
 [browser_animation_getSubTreeAnimations.js]
 [browser_animation_keepFinished.js]
-[browser_animation_name.js]
 [browser_animation_playerState.js]
 [browser_animation_playPauseIframe.js]
 [browser_animation_playPauseSeveral.js]
 [browser_animation_reconstructState.js]
 [browser_animation_refreshTransitions.js]
 [browser_animation_setCurrentTime.js]
 [browser_animation_setPlaybackRate.js]
 [browser_animation_simple.js]
deleted file mode 100644
--- a/devtools/server/tests/browser/browser_animation_name.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that the AnimationPlayerActor provides the correct name for an
-// animation. Whether this animation is a CSSAnimation, CSSTransition or a
-// script-based animation that has been given an id, or even a CSSAnimation that
-// has been given an id.
-
-const TEST_DATA = [{
-  selector: ".simple-animation",
-  animationIndex: 0,
-  expectedName: "move"
-}, {
-  selector: ".transition",
-  animationIndex: 0,
-  expectedName: "width"
-}, {
-  selector: ".script-generated",
-  animationIndex: 0,
-  expectedName: "custom-animation-name"
-}, {
-  selector: ".delayed-animation",
-  animationIndex: 0,
-  expectedName: "cssanimation-custom-name"
-}];
-
-add_task(function*() {
-  let {client, walker, animations} =
-    yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
-
-  for (let {selector, animationIndex, expectedName} of TEST_DATA) {
-    let {name} = yield getAnimationStateForNode(walker, animations, selector,
-                                                animationIndex);
-    is(name, expectedName, "The animation has the expected name");
-  }
-
-  yield closeDebuggerClient(client);
-  gBrowser.removeCurrentTab();
-});
-
-function* getAnimationStateForNode(walker, animations, nodeSelector, index) {
-  let node = yield walker.querySelector(walker.rootNode, nodeSelector);
-  let players = yield animations.getAnimationPlayersForNode(node);
-  let player = players[index];
-  yield player.ready();
-  let state = yield player.getCurrentState();
-  return state;
-}
--- a/devtools/server/tests/mochitest/test_inspector-mutations-events.html
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-events.html
@@ -9,17 +9,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 
 window.onload = function() {
 
   const Cu = Components.utils;
-  Cu.import("resource://gre/modules/devtools/Loader.jsm");
+  Cu.import("resource://devtools/shared/Loader.jsm");
   const {InspectorFront} =
     devtools.require("devtools/server/actors/inspector");
 
   SimpleTest.waitForExplicitFinish();
 
   let inspectee = null;
   let inspector = null;
   let walker = null;
--- a/devtools/server/tests/mochitest/test_inspector-resize.html
+++ b/devtools/server/tests/mochitest/test_inspector-resize.html
@@ -1,80 +1,79 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-Test that the inspector actor emits "resize" events when the page is resized.
-https://bugzilla.mozilla.org/show_bug.cgi?id=1222409
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 1222409</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
-  <script type="application/javascript;version=1.8">
-window.onload = function() {
-  const Cu = Components.utils;
-  Cu.import("resource://gre/modules/devtools/Loader.jsm");
-  const {Promise: promise} =
-    Cu.import("resource://gre/modules/Promise.jsm", {});
-  const {InspectorFront} =
-    devtools.require("devtools/server/actors/inspector");
-  const {console} =
-    Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
-
-  SimpleTest.waitForExplicitFinish();
-
-  let win = null;
-  let inspector = null;
-
-  addAsyncTest(function* setup() {
-    info ("Setting up inspector and walker actors.");
-
-    let url = document.getElementById("inspectorContent").href;
-
-    yield new Promise(resolve => {
-      attachURL(url, function(err, client, tab, doc) {
-        win = doc.defaultView;
-        inspector = InspectorFront(client, tab);
-        resolve();
-      });
-    });
-
-    runNextTest();
-  });
-
-  addAsyncTest(function*() {
-    let walker = yield inspector.getWalker();
-
-    // We can't receive events from the walker if we haven't first executed a
-    // method on the actor to initialize it.
-    yield walker.querySelector(walker.rootNode, "img");
-
-    let {outerWidth, outerHeight} = win;
-    let onResize = new Promise(resolve => {
-      walker.once("resize", () => {
-        resolve();
-      });
-    });
-    win.resizeTo(800, 600);
-    yield onResize;
-
-    ok(true, "The resize event was emitted");
-    win.resizeTo(outerWidth, outerHeight);
-
-    runNextTest();
-  });
-
-  runNextTest();
-};
-  </script>
-</head>
-<body>
-<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor emits "resize" events when the page is resized.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222409
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1222409</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+window.onload = function() {
+  const Cu = Components.utils;
+  Cu.import("resource://devtools/shared/Loader.jsm");
+  const {Promise: promise} =
+    Cu.import("resource://gre/modules/Promise.jsm", {});
+  const {InspectorFront} =
+    devtools.require("devtools/server/actors/inspector");
+  const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+  SimpleTest.waitForExplicitFinish();
+
+  let win = null;
+  let inspector = null;
+
+  addAsyncTest(function* setup() {
+    info ("Setting up inspector and walker actors.");
+
+    let url = document.getElementById("inspectorContent").href;
+
+    yield new Promise(resolve => {
+      attachURL(url, function(err, client, tab, doc) {
+        win = doc.defaultView;
+        inspector = InspectorFront(client, tab);
+        resolve();
+      });
+    });
+
+    runNextTest();
+  });
+
+  addAsyncTest(function*() {
+    let walker = yield inspector.getWalker();
+
+    // We can't receive events from the walker if we haven't first executed a
+    // method on the actor to initialize it.
+    yield walker.querySelector(walker.rootNode, "img");
+
+    let {outerWidth, outerHeight} = win;
+    let onResize = new Promise(resolve => {
+      walker.once("resize", () => {
+        resolve();
+      });
+    });
+    win.resizeTo(800, 600);
+    yield onResize;
+
+    ok(true, "The resize event was emitted");
+    win.resizeTo(outerWidth, outerHeight);
+
+    runNextTest();
+  });
+
+  runNextTest();
+};
+  </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/devtools/server/tests/mochitest/test_inspector-search-front.html
+++ b/devtools/server/tests/mochitest/test_inspector-search-front.html
@@ -7,23 +7,22 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug 835896</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 window.onload = function() {
   const Cu = Components.utils;
-  Cu.import("resource://gre/modules/devtools/Loader.jsm");
+  Cu.import("resource://devtools/shared/Loader.jsm");
   const {Promise: promise} =
     Cu.import("resource://gre/modules/Promise.jsm", {});
   const {InspectorFront} =
     devtools.require("devtools/server/actors/inspector");
-  const {console} =
-    Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
+  const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 
   SimpleTest.waitForExplicitFinish();
 
   let walkerFront = null;
   let inspectee = null;
   let inspector = null;
 
   // WalkerFront specific tests.  These aren't to excercise search
--- a/devtools/server/tests/mochitest/test_inspector-search.html
+++ b/devtools/server/tests/mochitest/test_inspector-search.html
@@ -7,25 +7,24 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug 835896</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 window.onload = function() {
   const Cu = Components.utils;
-  Cu.import("resource://gre/modules/devtools/Loader.jsm");
+  Cu.import("resource://devtools/shared/Loader.jsm");
   const {Promise: promise} =
     Cu.import("resource://gre/modules/Promise.jsm", {});
   const {InspectorFront} =
     devtools.require("devtools/server/actors/inspector");
   const {WalkerSearch, WalkerIndex} =
     devtools.require("devtools/server/actors/utils/walker-search");
-  const {console} =
-    Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
+  const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 
   SimpleTest.waitForExplicitFinish();
 
   let walkerActor = null;
   let walkerSearch = null;
   let inspectee = null;
   let inspector = null;
 
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_animation_name.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that AnimationPlayerActor.getName returns the right name depending on
+// the type of an animation and the various properties available on it.
+
+const { AnimationPlayerActor } = require("devtools/server/actors/animation");
+
+function run_test() {
+  // Mock a window with just the properties the AnimationPlayerActor uses.
+  let window = {
+    MutationObserver: function() {
+      this.observe = () => {};
+    },
+    Animation: function() {
+      this.effect = {target: getMockNode()};
+    },
+    CSSAnimation: function() {
+      this.effect = {target: getMockNode()};
+    },
+    CSSTransition: function() {
+      this.effect = {target: getMockNode()};
+    }
+  };
+
+  // Helper to get a mock DOM node.
+  function getMockNode() {
+    return {
+      ownerDocument: {
+        defaultView: window
+      }
+    };
+  }
+
+  // Objects in this array should contain the following properties:
+  // - desc {String} For logging
+  // - animation {Object} An animation object instantiated from one of the mock
+  //   window animation constructors.
+  // - props {Objet} Properties of this object will be added to the animation
+  //   object.
+  // - expectedName {String} The expected name returned by
+  //   AnimationPlayerActor.getName.
+  const TEST_DATA = [{
+    desc: "Animation with an id",
+    animation: new window.Animation(),
+    props: { id: "animation-id" },
+    expectedName: "animation-id"
+  }, {
+    desc: "CSSTransition with an id",
+    animation: new window.CSSTransition(),
+    props: { id: "transition-with-id", transitionProperty: "width" },
+    expectedName: "transition-with-id"
+  }, {
+    desc: "CSSAnimation with an id",
+    animation: new window.CSSAnimation(),
+    props: { id: "animation-with-id", animationName: "move" },
+    expectedName: "animation-with-id"
+  }, {
+    desc: "CSSTransition without an id",
+    animation: new window.CSSTransition(),
+    props: { transitionProperty: "width" },
+    expectedName: "width"
+  }, {
+    desc: "CSSAnimation without an id",
+    animation: new window.CSSAnimation(),
+    props: { animationName: "move" },
+    expectedName: "move"
+  }];
+
+  for (let { desc, animation, props, expectedName } of TEST_DATA) {
+    do_print(desc);
+    for (let key in props) {
+      animation[key] = props[key];
+    }
+    let actor = AnimationPlayerActor({}, animation);
+    do_check_eq(actor.getName(), expectedName);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_animation_type.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the output of AnimationPlayerActor.getType().
+
+const { ANIMATION_TYPES, AnimationPlayerActor } =
+  require("devtools/server/actors/animation");
+
+function run_test() {
+  // Mock a window with just the properties the AnimationPlayerActor uses.
+  let window = {
+    MutationObserver: function() {
+      this.observe = () => {};
+    },
+    Animation: function() {
+      this.effect = {target: getMockNode()};
+    },
+    CSSAnimation: function() {
+      this.effect = {target: getMockNode()};
+    },
+    CSSTransition: function() {
+      this.effect = {target: getMockNode()};
+    }
+  };
+
+  // Helper to get a mock DOM node.
+  function getMockNode() {
+    return {
+      ownerDocument: {
+        defaultView: window
+      }
+    };
+  }
+
+  // Objects in this array should contain the following properties:
+  // - desc {String} For logging
+  // - animation {Object} An animation object instantiated from one of the mock
+  //   window animation constructors.
+  // - expectedType {String} The expected type returned by
+  //   AnimationPlayerActor.getType.
+  const TEST_DATA = [{
+    desc: "Test CSSAnimation type",
+    animation: new window.CSSAnimation(),
+    expectedType: ANIMATION_TYPES.CSS_ANIMATION
+  }, {
+    desc: "Test CSSTransition type",
+    animation: new window.CSSTransition(),
+    expectedType: ANIMATION_TYPES.CSS_TRANSITION
+  }, {
+    desc: "Test unknown type",
+    animation: {effect: {target: getMockNode()}},
+    expectedType: ANIMATION_TYPES.UNKNOWN
+  }];
+
+  for (let { desc, animation, expectedType } of TEST_DATA) {
+    do_print(desc);
+    let actor = AnimationPlayerActor({}, animation);
+    do_check_eq(actor.getType(), expectedType);
+  }
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -28,16 +28,18 @@ support-files =
   setBreakpoint-on-line.js
   setBreakpoint-on-line-in-gcd-script.js
   setBreakpoint-on-line-with-multiple-offsets.js
   setBreakpoint-on-line-with-multiple-statements.js
   setBreakpoint-on-line-with-no-offsets.js
   setBreakpoint-on-line-with-no-offsets-at-end-of-script.js
   setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
 
+[test_animation_name.js]
+[test_animation_type.js]
 [test_ScriptStore.js]
 [test_actor-registry-actor.js]
 [test_nesting-01.js]
 [test_nesting-02.js]
 [test_nesting-03.js]
 [test_forwardingprefix.js]
 [test_getyoungestframe.js]
 [test_nsjsinspector.js]
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -186,17 +186,17 @@ pref("dom.experimental_forms", true);
 pref("dom.forms.number", true);
 
 /* extension manager and xpinstall */
 pref("xpinstall.whitelist.directRequest", false);
 pref("xpinstall.whitelist.fileRequest", false);
 pref("xpinstall.whitelist.add", "https://addons.mozilla.org");
 pref("xpinstall.whitelist.add.180", "https://marketplace.firefox.com");
 
-pref("xpinstall.signatures.required", false);
+pref("xpinstall.signatures.required", true);
 
 pref("extensions.enabledScopes", 1);
 pref("extensions.autoupdate.enabled", true);
 pref("extensions.autoupdate.interval", 86400);
 pref("extensions.update.enabled", true);
 pref("extensions.update.interval", 86400);
 pref("extensions.dss.enabled", false);
 pref("extensions.dss.switchPending", false);
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1,17 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.Manifest;
-import android.os.AsyncTask;
 import android.support.annotation.NonNull;
 import org.json.JSONArray;
 import org.mozilla.gecko.adjust.AdjustHelperInterface;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
@@ -29,17 +28,19 @@ import org.mozilla.gecko.favicons.Favico
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
 import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.home.BrowserSearch;
 import org.mozilla.gecko.home.HomeBanner;
+import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.home.HomeConfig.PanelType;
+import org.mozilla.gecko.home.HomeConfigPrefsBackend;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.HomePanelsManager;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.javaaddons.JavaAddonManager;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
@@ -175,16 +176,17 @@ public class BrowserApp extends GeckoApp
                                    ActionModeCompat.Presenter,
                                    LayoutInflater.Factory {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     private static final int TABS_ANIMATION_DURATION = 450;
 
     private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast";
     public static final String GUEST_BROWSING_ARG = "--guest";
+    public static final String INTENT_KEY_SWITCHBOARD_UUID = "switchboard-uuid";
 
     private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
 
     private static final String BROWSER_SEARCH_TAG = "browser_search";
 
     // Request ID for startActivityForResult.
     private static final int ACTIVITY_REQUEST_PREFERENCES = 1001;
     private static final int ACTIVITY_REQUEST_TAB_QUEUE = 2001;
@@ -197,17 +199,17 @@ public class BrowserApp extends GeckoApp
     private View mBrowserSearchContainer;
 
     public ViewGroup mBrowserChrome;
     public ViewFlipper mActionBarFlipper;
     public ActionModeCompatView mActionBar;
     private BrowserToolbar mBrowserToolbar;
     private View mDoorhangerOverlay;
     // We can't name the TabStrip class because it's not included on API 9.
-    private Refreshable mTabStrip;
+    private TabStripInterface mTabStrip;
     private ToolbarProgressView mProgressView;
     private FirstrunAnimationContainer mFirstrunAnimationContainer;
     private HomePager mHomePager;
     private TabsPanel mTabsPanel;
     private ViewGroup mHomePagerContainer;
     private ActionModeCompat mActionMode;
     private TabHistoryController tabHistoryController;
     private ZoomedView mZoomedView;
@@ -584,22 +586,25 @@ public class BrowserApp extends GeckoApp
         super.onCreate(savedInstanceState);
 
         final Context appContext = getApplicationContext();
 
         if (AppConstants.MOZ_SWITCHBOARD) {
             // Initializes the default URLs the first time.
             SwitchBoard.initDefaultServerUrls("https://switchboard-server.dev.mozaws.net/urls", "https://switchboard-server.dev.mozaws.net/v1", true);
 
+            final String switchboardUUID = ContextUtils.getStringExtra(intent, INTENT_KEY_SWITCHBOARD_UUID);
+            SwitchBoard.setUUIDFromExtra(switchboardUUID);
+
             // Looks at the server if there are changes in the server URL that should be used in the future
-            new AsyncConfigLoader(this, AsyncConfigLoader.UPDATE_SERVER).execute();
+            new AsyncConfigLoader(this, AsyncConfigLoader.UPDATE_SERVER, switchboardUUID).execute();
 
             // Loads the actual config. This can be done on app start or on app onResume() depending
             // how often you want to update the config.
-            new AsyncConfigLoader(this, AsyncConfigLoader.CONFIG_SERVER).execute();
+            new AsyncConfigLoader(this, AsyncConfigLoader.CONFIG_SERVER, switchboardUUID).execute();
         }
 
         mBrowserChrome = (ViewGroup) findViewById(R.id.browser_chrome);
         mActionBarFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
         mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
 
         mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
         mProgressView = (ToolbarProgressView) findViewById(R.id.progress);
@@ -630,17 +635,17 @@ public class BrowserApp extends GeckoApp
             showTabQueuePromptIfApplicable(intent);
         } else if (GuestSession.NOTIFICATION_INTENT.equals(action)) {
             GuestSession.handleIntent(this, intent);
         } else if (TabQueueHelper.LOAD_URLS_ACTION.equals(action)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "tabqueue");
         }
 
         if (HardwareUtils.isTablet()) {
-            mTabStrip = (Refreshable) (((ViewStub) findViewById(R.id.tablet_tab_strip)).inflate());
+            mTabStrip = (TabStripInterface) (((ViewStub) findViewById(R.id.tablet_tab_strip)).inflate());
         }
 
         ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener());
         ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
             @Override
             public boolean onInterceptMotionEvent(View view, MotionEvent event) {
                 // If we get a gamepad panning MotionEvent while the focus is not on the layerview,
                 // put the focus on the layerview and carry on
@@ -848,16 +853,26 @@ public class BrowserApp extends GeckoApp
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
 
         try {
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
 
             if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, false)) {
                 if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
                     showFirstrunPager();
+
+                    if (HardwareUtils.isTablet()) {
+                        mTabStrip.setOnTabChangedListener(new TabStripInterface.OnTabAddedOrRemovedListener() {
+                            @Override
+                            public void onTabChanged() {
+                                hideFirstrunPager(TelemetryContract.Method.BUTTON);
+                                mTabStrip.setOnTabChangedListener(null);
+                            }
+                        });
+                    }
                 }
                 // Don't bother trying again to show the v1 minimal first run.
                 prefs.edit().putBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, false).apply();
             }
         } finally {
             StrictMode.setThreadPolicy(savedPolicy);
         }
     }
@@ -886,18 +901,17 @@ public class BrowserApp extends GeckoApp
             return;
         }
 
         if (mActionMode != null) {
             endActionModeCompat();
             return;
         }
 
-        if (hideFirstrunPager()) {
-            Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, "firstrun-pane");
+        if (hideFirstrunPager(TelemetryContract.Method.BACK)) {
             return;
         }
 
         super.onBackPressed();
     }
 
     @Override
     public void onAttachedToWindow() {
@@ -1219,16 +1233,18 @@ public class BrowserApp extends GeckoApp
         enterEditingMode();
         return true;
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         final int itemId = item.getItemId();
         if (itemId == R.id.pasteandgo) {
+            hideFirstrunPager(TelemetryContract.Method.CONTEXT_MENU);
+
             String text = Clipboard.getText();
             if (!TextUtils.isEmpty(text)) {
                 loadUrlOrKeywordSearch(text);
                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "pasteandgo");
             }
             return true;
         }
@@ -1740,16 +1756,19 @@ public class BrowserApp extends GeckoApp
             final BrowserDB db = getProfile().getDB();
             final ContentResolver cr = getContentResolver();
             Telemetry.addToHistogram("PLACES_PAGES_COUNT", db.getCount(cr, "history"));
             Telemetry.addToHistogram("FENNEC_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks"));
             Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getReadingListAccessor().getCount(cr));
             Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
             Telemetry.addToHistogram("FENNEC_TABQUEUE_ENABLED", (TabQueueHelper.isTabQueueEnabled(BrowserApp.this) ? 1 : 0));
             Telemetry.addToHistogram("FENNEC_CUSTOM_HOMEPAGE", (TextUtils.isEmpty(getHomepage()) ? 0 : 1));
+            final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
+            final boolean hasCustomHomepanels = prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY) || prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
+            Telemetry.addToHistogram("FENNEC_HOMEPANELS_CUSTOM", hasCustomHomepanels ? 1 : 0);
 
             if (Versions.feature16Plus) {
                 Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0));
             }
 
             if (Restrictions.isRestrictedProfile(this)) {
                 for (Restrictable rest : RestrictedProfileConfiguration.getVisibleRestrictions()) {
                     int value = Restrictions.isAllowed(this, rest) ? 1 : 0;
@@ -1992,16 +2011,18 @@ public class BrowserApp extends GeckoApp
 
         return true;
     }
 
     private void showTabs(final TabsPanel.Panel panel) {
         if (Tabs.getInstance().getDisplayCount() == 0)
             return;
 
+        hideFirstrunPager(TelemetryContract.Method.TABSTRAY);
+
         if (ensureTabsPanelExists()) {
             // If we've just inflated the tabs panel, only show it once the current
             // layout pass is done to avoid displayed temporary UI states during
             // relayout.
             ViewTreeObserver vto = mTabsPanel.getViewTreeObserver();
             if (vto.isAlive()) {
                 vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
@@ -2223,20 +2244,16 @@ public class BrowserApp extends GeckoApp
     }
 
     /**
      * Enters editing mode with the current tab's URL. There might be no
      * tabs loaded by the time the user enters editing mode e.g. just after
      * the app starts. In this case, we simply fallback to an empty URL.
      */
     private void enterEditingMode() {
-        if (hideFirstrunPager()) {
-            Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.ACTIONBAR, "firstrun-pane");
-        }
-
         String url = "";
         String telemetryMsg = "urlbar-empty";
 
         final Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null) {
             final String userSearchTerm = tab.getUserRequested();
             final String tabURL = tab.getURL();
 
@@ -2255,16 +2272,18 @@ public class BrowserApp extends GeckoApp
         Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.ACTIONBAR, telemetryMsg);
     }
 
     /**
      * Enters editing mode with the specified URL. If a null
      * url is given, the empty String will be used instead.
      */
     private void enterEditingMode(@NonNull String url) {
+        hideFirstrunPager(TelemetryContract.Method.ACTIONBAR);
+
         if (mBrowserToolbar.isEditing() || mBrowserToolbar.isAnimating()) {
             return;
         }
 
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         final String panelId;
         if (selectedTab != null) {
             mTargetTabForEditingMode = selectedTab.getId();
@@ -2647,21 +2666,30 @@ public class BrowserApp extends GeckoApp
     }
 
     private void hideWebContent() {
         // The view is set to INVISIBLE, rather than GONE, to avoid
         // the additional requestLayout() call.
         mLayerView.setVisibility(View.INVISIBLE);
     }
 
-    public boolean hideFirstrunPager() {
+    /**
+     * Hide the Onboarding pager on user action, and don't show any onFinish hints.
+     * @param method TelemetryContract method by which action was taken
+     * @return boolean of whether pager was visible
+     */
+    private boolean hideFirstrunPager(TelemetryContract.Method method) {
         if (!isFirstrunVisible()) {
             return false;
         }
 
+        Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, method, "firstrun-pane");
+
+        // Don't show any onFinish actions when hiding from this Activity.
+        mFirstrunAnimationContainer.registerOnFinishListener(null);
         mFirstrunAnimationContainer.hide();
         return true;
     }
 
     /**
      * Hides the HomePager, using the url of the currently selected tab as the url to be
      * loaded.
      */
@@ -3009,16 +3037,18 @@ public class BrowserApp extends GeckoApp
             quickShare.setActionProvider(provider);
         }
 
         return true;
     }
 
     @Override
     public void openOptionsMenu() {
+        hideFirstrunPager(TelemetryContract.Method.MENU);
+
         // Disable menu access (for hardware buttons) when the software menu button is inaccessible.
         // Note that the software button is always accessible on new tablet.
         if (mBrowserToolbar.isEditing() && !HardwareUtils.isTablet()) {
             return;
         }
 
         if (ActivityUtils.isFullScreen(this)) {
             return;
@@ -3652,17 +3682,17 @@ public class BrowserApp extends GeckoApp
         final boolean isBookmarkAction = GeckoApp.ACTION_HOMESCREEN_SHORTCUT.equals(action);
         final boolean isTabQueueAction = TabQueueHelper.LOAD_URLS_ACTION.equals(action);
 
         if (mInitialized && (isViewAction || isBookmarkAction)) {
             // Dismiss editing mode if the user is loading a URL from an external app.
             mBrowserToolbar.cancelEdit();
 
             // Hide firstrun-pane if the user is loading a URL from an external app.
-            hideFirstrunPager();
+            hideFirstrunPager(TelemetryContract.Method.NONE);
 
             if (isBookmarkAction) {
                 // GeckoApp.ACTION_HOMESCREEN_SHORTCUT means we're opening a bookmark that
                 // was added to Android's homescreen.
                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.HOMESCREEN);
             }
         }
 
@@ -3950,18 +3980,22 @@ public class BrowserApp extends GeckoApp
         i.putExtra(TelemetryConstants.EXTRA_PROFILE_PATH, profile.getDir().toString());
         i.putExtra(TelemetryConstants.EXTRA_SEQ, seq);
         startService(i);
 
         // Intent redelivery will ensure this value gets used - see TelemetryUploadService class comments for details.
         sharedPrefs.edit().putInt(TelemetryConstants.PREF_SEQ_COUNT, seq + 1).apply();
     }
 
-    public static interface Refreshable {
+    public static interface TabStripInterface {
         public void refresh();
+        void setOnTabChangedListener(OnTabAddedOrRemovedListener listener);
+        interface OnTabAddedOrRemovedListener {
+            void onTabChanged();
+        }
     }
 
     @Override
     protected StartupAction getStartupAction(final String passedURL) {
         final boolean inGuestMode = GeckoProfile.get(this).inGuestMode();
         if (inGuestMode) {
             return StartupAction.GUEST;
         }
--- a/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
@@ -1,47 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
-import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.GeckoRequest;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
-import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.CheckedTextView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener  {
     private static final String LOGTAG = "GeckoFindInPageBar";
     private static final String REQUEST_ID = "FindInPageBar";
 
-    // Will be removed by Bug 1113297.
-    private static final boolean MATCH_CASE_ENABLED = AppConstants.NIGHTLY_BUILD;
-
     private final Context mContext;
     private CustomEditText mFindText;
-    private CheckedTextView mMatchCase;
     private TextView mStatusText;
     private boolean mInflated;
 
     public FindInPageBar(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
         setFocusable(true);
     }
@@ -66,23 +59,16 @@ public class FindInPageBar extends Linea
                 if (keyCode == KeyEvent.KEYCODE_BACK) {
                     hide();
                     return true;
                 }
                 return false;
             }
         });
 
-        mMatchCase = (CheckedTextView) content.findViewById(R.id.find_matchcase);
-        if (MATCH_CASE_ENABLED) {
-            mMatchCase.setOnClickListener(this);
-        } else {
-            mMatchCase.setVisibility(View.GONE);
-        }
-
         mStatusText = (TextView) content.findViewById(R.id.find_status);
 
         mInflated = true;
         EventDispatcher.getInstance().registerGeckoThreadListener(this, "TextSelection:Data");
     }
 
     public void show() {
         if (!mInflated)
@@ -148,25 +134,16 @@ public class FindInPageBar extends Linea
 
     @Override
     public void onClick(View v) {
         final int viewId = v.getId();
 
         String extras = getResources().getResourceEntryName(viewId);
         Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, extras);
 
-        if (viewId == R.id.find_matchcase) {
-            // Toggle matchcase state (color).
-            mMatchCase.toggle();
-
-            // Repeat the find after a matchcase change.
-            sendRequestToFinderHelper("FindInPage:Find", mFindText.getText().toString());
-            return;
-        }
-
         if (viewId == R.id.find_prev) {
             sendRequestToFinderHelper("FindInPage:Prev", mFindText.getText().toString());
             getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
             return;
         }
 
         if (viewId == R.id.find_next) {
             sendRequestToFinderHelper("FindInPage:Next", mFindText.getText().toString());
@@ -218,26 +195,17 @@ public class FindInPageBar extends Linea
             });
         }
     }
 
     /**
      * Request find operation, and update matchCount results (current count and total).
      */
     private void sendRequestToFinderHelper(final String request, final String searchString) {
-        final JSONObject json = new JSONObject();
-        try {
-            json.put("searchString", searchString);
-            json.put("matchCase", mMatchCase.isChecked());
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "JSON error - Error creating JSONObject", e);
-            return;
-        }
-
-        GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, json) {
+        GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, searchString) {
             @Override
             public void onResponse(NativeJSObject nativeJSObject) {
                 final int total = nativeJSObject.optInt("total", 0);
                 if (total == -1) {
                     final int limit = nativeJSObject.optInt("limit", 0);
                     updateResult(Integer.toString(limit) + "+");
                 } else if (total > 0) {
                     final int current = nativeJSObject.optInt("current", 0);
--- a/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
@@ -12,20 +12,19 @@ import java.net.URL;
 import java.net.URLEncoder;
 import java.util.ArrayList;
 
 import org.json.JSONArray;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
 import android.text.TextUtils;
 import android.util.Log;
+import org.mozilla.gecko.util.NetworkUtils;
 
 /**
  * Use network-based search suggestions.
  */
 public class SuggestClient {
     private static final String LOGTAG = "GeckoSuggestClient";
 
     // This should go through GeckoInterface to get the UA, but the search activity
@@ -65,17 +64,17 @@ public class SuggestClient {
         if (query.equals(mPrevQuery))
             return mPrevResults;
 
         ArrayList<String> suggestions = new ArrayList<String>();
         if (TextUtils.isEmpty(mSuggestTemplate) || TextUtils.isEmpty(query)) {
             return suggestions;
         }
 
-        if (!isNetworkConnected() && mCheckNetwork) {
+        if (!NetworkUtils.isConnected(mContext) && mCheckNetwork) {
             Log.i(LOGTAG, "Not connected to network");
             return suggestions;
         }
 
         try {
             String encoded = URLEncoder.encode(query, "UTF-8");
             String suggestUri = mSuggestTemplate.replace("__searchTerms__", encoded);
 
@@ -124,29 +123,16 @@ public class SuggestClient {
             Log.e(LOGTAG, "Error", e);
         }
 
         mPrevQuery = query;
         mPrevResults = suggestions;
         return suggestions;
     }
 
-    private boolean isNetworkConnected() {
-        NetworkInfo networkInfo = getActiveNetworkInfo();
-        return networkInfo != null && networkInfo.isConnected();
-    }
-
-    private NetworkInfo getActiveNetworkInfo() {
-        ConnectivityManager connectivity = (ConnectivityManager) mContext
-                .getSystemService(Context.CONNECTIVITY_SERVICE);
-        if (connectivity == null)
-            return null;
-        return connectivity.getActiveNetworkInfo();
-    }
-
     private String convertStreamToString(java.io.InputStream is) {
         try {
             return new java.util.Scanner(is).useDelimiter("\\A").next();
         } catch (java.util.NoSuchElementException e) {
             return "";
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/TelemetryContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/TelemetryContract.java
@@ -183,16 +183,19 @@ public interface TelemetryContract {
         SETTINGS("settings"),
 
         // Actions triggered from the share overlay.
         SHARE_OVERLAY("shareoverlay"),
 
         // Action triggered from a suggestion provided to the user.
         SUGGESTION("suggestion"),
 
+        // Action triggered from the Tabs tray.
+        TABSTRAY("tabstray"),
+
         // Action triggered from a SuperToast.
         // Note: Only used in JavaScript for now, but here for completeness.
         TOAST("toast"),
 
         // Action triggerred by pressing a SearchWidget button
         WIDGET("widget"),
 
         // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -34,17 +34,19 @@ import android.database.sqlite.SQLiteOpe
 import android.net.Uri;
 import android.os.Build;
 import android.util.Log;
 
 
 final class BrowserDatabaseHelper extends SQLiteOpenHelper {
     private static final String LOGTAG = "GeckoBrowserDBHelper";
 
-    public static final int DATABASE_VERSION = 27; // Bug 1128675
+    // Replace the Bug number below with your Bug that is conducting a DB upgrade, as to force a merge conflict with any
+    // other patches that require a DB upgrade.
+    public static final int DATABASE_VERSION = 27; // Bug 826400
     public static final String DATABASE_NAME = "browser.db";
 
     final protected Context mContext;
 
     static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
@@ -2,30 +2,32 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import java.util.EnumSet;
 
+import android.util.Log;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.HeaderViewListAdapter;
 import android.widget.ListAdapter;
+import org.mozilla.gecko.util.NetworkUtils;
 
 /**
  * A ListView of bookmarks.
  */
 public class BookmarksListView extends HomeListView
                                implements AdapterView.OnItemClickListener{
     public static final String LOGTAG = "GeckoBookmarksListView";
 
@@ -88,16 +90,17 @@ public class BookmarksListView extends H
             final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
             final String folderTitle = adapter.getFolderTitle(parent.getContext(), cursor);
             adapter.moveToChildFolder(folderId, folderTitle);
         } else {
             // Otherwise, just open the URL
             final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
 
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "bookmarks");
+            Telemetry.addToHistogram("FENNEC_LOAD_SAVED_PAGE", NetworkUtils.isConnected(getContext()) ? 2 : 3);
 
             // This item is a TwoLinePageRow, so we allow switch-to-tab.
             getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
         }
     }
 
     @Override
     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
@@ -28,27 +28,27 @@ import android.content.BroadcastReceiver
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.support.v4.content.LocalBroadcastManager;
 import android.text.TextUtils;
 import android.util.Log;
 
-class HomeConfigPrefsBackend implements HomeConfigBackend {
+public class HomeConfigPrefsBackend implements HomeConfigBackend {
     private static final String LOGTAG = "GeckoHomeConfigBackend";
 
     // Increment this to trigger a migration.
     private static final int VERSION = 3;
 
     // This key was originally used to store only an array of panel configs.
-    private static final String PREFS_CONFIG_KEY_OLD = "home_panels";
+    public static final String PREFS_CONFIG_KEY_OLD = "home_panels";
 
     // This key is now used to store a version number with the array of panel configs.
-    private static final String PREFS_CONFIG_KEY = "home_panels_with_version";
+    public static final String PREFS_CONFIG_KEY = "home_panels_with_version";
 
     // Keys used with JSON object stored in prefs.
     private static final String JSON_KEY_PANELS = "panels";
     private static final String JSON_KEY_VERSION = "version";
 
     private static final String PREFS_LOCALE_KEY = "home_locale";
 
     private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
--- a/mobile/android/base/java/org/mozilla/gecko/home/ReadingListPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/ReadingListPanel.java
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import java.util.EnumSet;
 
+import android.util.Log;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.ReaderModeUtils;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.db.BrowserContract.URLColumns;
 import org.mozilla.gecko.db.ReadingListAccessor;
@@ -29,16 +30,17 @@ import android.text.Spanned;
 import android.text.style.ImageSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.AdapterView;
 import android.widget.ImageView;
 import android.widget.TextView;
+import org.mozilla.gecko.util.NetworkUtils;
 
 /**
  * Fragment that displays reading list contents in a ListView.
  */
 public class ReadingListPanel extends HomeFragment {
 
     // Cursor loader ID for reading list
     private static final int LOADER_ID_READING_LIST = 0;
@@ -87,16 +89,17 @@ public class ReadingListPanel extends Ho
                 if (c == null || !c.moveToPosition(position)) {
                     return;
                 }
 
                 String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
                 url = ReaderModeUtils.getAboutReaderForUrl(url);
 
                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "reading_list");
+                Telemetry.addToHistogram("FENNEC_LOAD_SAVED_PAGE", NetworkUtils.isConnected(context) ? 0 : 1);
 
                 // This item is a TwoLinePageRow, so we allow switch-to-tab.
                 mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
 
                 markAsRead(context, id);
             }
         });
 
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
@@ -10,32 +10,34 @@ import android.graphics.drawable.Drawabl
 import android.graphics.drawable.StateListDrawable;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
-import org.mozilla.gecko.BrowserApp.Refreshable;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.BrowserApp.TabStripInterface;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.ColorUtils;
 import org.mozilla.gecko.widget.themed.ThemedImageButton;
 import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
 
 public class TabStrip extends ThemedLinearLayout
-                      implements Refreshable {
+                      implements TabStripInterface {
     private static final String LOGTAG = "GeckoTabStrip";
 
     private final TabStripView tabStripView;
     private final ThemedImageButton addTabButton;
 
     private final TabsListener tabsListener;
+    private OnTabAddedOrRemovedListener tabChangedListener;
 
     public TabStrip(Context context) {
         this(context, null);
     }
 
     public TabStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOrientation(HORIZONTAL);
@@ -95,30 +97,40 @@ public class TabStrip extends ThemedLine
     }
 
     @Override
     public void setPrivateMode(boolean isPrivate) {
         super.setPrivateMode(isPrivate);
         addTabButton.setPrivateMode(isPrivate);
     }
 
+    public void setOnTabChangedListener(OnTabAddedOrRemovedListener listener) {
+        tabChangedListener = listener;
+    }
+
     private class TabsListener implements Tabs.OnTabsChangedListener {
         @Override
         public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
             switch (msg) {
                 case RESTORED:
                     tabStripView.restoreTabs();
                     break;
 
                 case ADDED:
                     tabStripView.addTab(tab);
+                    if (tabChangedListener != null) {
+                        tabChangedListener.onTabChanged();
+                    }
                     break;
 
                 case CLOSED:
                     tabStripView.removeTab(tab);
+                    if (tabChangedListener != null) {
+                        tabChangedListener.onTabChanged();
+                    }
                     break;
 
                 case SELECTED:
                     // Update the selected position, then fall through...
                     tabStripView.selectTab(tab);
                     setPrivateMode(tab.isPrivate());
                 case UNSELECTED:
                     // We just need to update the style for the unselected tab...
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/util/NetworkUtils.java
@@ -0,0 +1,28 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.util;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+public class NetworkUtils {
+
+    /**
+     * Indicates whether network connectivity exists and it is possible to establish connections and pass data.
+     */
+    public static boolean isConnected(Context context) {
+        final ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (connectivity == null) {
+            return false;
+        }
+        final NetworkInfo networkInfo = connectivity.getActiveNetworkInfo();
+        if (networkInfo == null) {
+            return false;
+        }
+        return networkInfo.isConnected();
+    }
+}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -713,21 +713,16 @@ just addresses the organization to follo
      time the tabs were last synced relative to the current time; examples
      include "42 minutes ago", "4 days ago", "last week", etc. The subject of
      "Last synced" is one of the user's other Sync clients, typically Firefox on
      their desktop or laptop.-->
 <!ENTITY remote_tabs_last_synced "Last synced: &formatS;">
 <!-- Localization note: Used when the sync has not happend yet, showed in place of a date -->
 <!ENTITY remote_tabs_never_synced "Last synced: never">
 
-<!-- Find-In-Page strings -->
-<!-- LOCALIZATION NOTE (find_matchcase): This is meant to appear as an icon that changes color
-     if match-case is activated. i.e. No more than two letters, one uppercase, one lowercase. -->
-<!ENTITY find_matchcase "Aa">
-
 <!ENTITY intent_uri_cannot_open "Cannot open link">
 <!-- LOCALIZATION NOTE (intent_uri_private_browsing_prompt): This string will
      appear in an alert when a user, who is currently in private browsing,
      clicks a link that will open an external Android application. "&formatS;"
      will be replaced with the name of the application that will be opened. -->
 <!ENTITY intent_uri_private_browsing_prompt "This link will open in &formatS;. Are you sure you want to exit Private Browsing?">
 <!-- LOCALIZATION NOTE (intent_uri_private_browsing_multiple_match_title): This
      string will appear as the title of an alert when a user, who is currently
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -111,16 +111,17 @@ gujar.sources += ['java/org/mozilla/geck
     'util/INISection.java',
     'util/InputOptionsUtils.java',
     'util/IOUtils.java',
     'util/JSONUtils.java',
     'util/MenuUtils.java',
     'util/NativeEventListener.java',
     'util/NativeJSContainer.java',
     'util/NativeJSObject.java',
+    'util/NetworkUtils.java',
     'util/NonEvictingLruCache.java',
     'util/PrefUtils.java',
     'util/ProxySelector.java',
     'util/RawResource.java',
     'util/StringUtils.java',
     'util/ThreadUtils.java',
     'util/UIAsyncTask.java',
     'util/WeakReferenceHandler.java',
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/find_matchcase_selector.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_checked="true"
-          android:color="@color/tabs_tray_icon_grey"/>
-
-    <item android:state_checked="false"
-          android:color="@color/find_matchcase_off"/>
-
-</selector>
--- a/mobile/android/base/resources/layout/find_in_page_content.xml
+++ b/mobile/android/base/resources/layout/find_in_page_content.xml
@@ -23,24 +23,16 @@
 
     <TextView android:id="@+id/find_status"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginRight="@dimen/find_in_page_status_margin_right"
               android:textColor="@color/tabs_tray_icon_grey"
               android:visibility="gone"/>
 
-    <CheckedTextView android:id="@+id/find_matchcase"
-                     android:layout_width="wrap_content"
-                     android:layout_height="wrap_content"
-                     android:padding="@dimen/find_in_page_matchcase_padding"
-                     android:checked="false"
-                     android:text="@string/find_matchcase"
-                     android:textColor="@drawable/find_matchcase_selector"/>
-
     <ImageButton android:id="@+id/find_prev"
                  style="@style/FindBar.ImageButton"
                  android:contentDescription="@string/find_prev"
                  android:layout_marginTop="@dimen/find_in_page_control_margin_top"
                  android:src="@drawable/find_prev"/>
 
     <ImageButton android:id="@+id/find_next"
                  style="@style/FindBar.ImageButton"
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -136,19 +136,16 @@
   <color name="toast_button_text">#FFFFFFFF</color>
 
   <!-- Tab History colors. -->
   <color name="tab_history_timeline_separator">#D7D9DB</color>
   <color name="tab_history_favicon_border">#D7D9DB</color>
   <color name="tab_history_favicon_background">#FFFFFF</color>
   <color name="tab_history_border_color">#DADADF</color>
 
-  <!-- Colour used for Find-In-Page dialog -->
-  <color name="find_matchcase_off">#D02626</color>
-
   <!-- Canvas delegate paint color -->
   <color name="canvas_delegate_paint">#FFFF0000</color>
 
   <!-- Top sites thumbnail colors -->
   <color name="top_site_default">#FFECF0F3</color>
   <color name="top_site_border">#FFCFD9E1</color>
 
   <color name="private_active_text">#FFFFFF</color>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -201,17 +201,16 @@
     <dimen name="drawable_dropshadow_size">3dp</dimen>
 
     <!-- Find-In-Page dialog dimensions. -->
     <dimen name="find_in_page_text_margin_left">5dip</dimen>
     <dimen name="find_in_page_text_margin_right">12dip</dimen>
     <dimen name="find_in_page_text_padding_left">10dip</dimen>
     <dimen name="find_in_page_text_padding_right">10dip</dimen>
     <dimen name="find_in_page_status_margin_right">10dip</dimen>
-    <dimen name="find_in_page_matchcase_padding">10dip</dimen>
     <dimen name="find_in_page_control_margin_top">2dip</dimen>
 
     <!-- The share icon asset has no padding while the other action bar items do
          so we dynamically add padding to compensate. To be removed in bug 1122752. -->
     <dimen name="ab_share_padding">12dp</dimen>
 
     <dimen name="progress_bar_scroll_offset">1.5dp</dimen>
 
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -111,17 +111,16 @@
   <string name="history_older_section">&history_older_section3;</string>
 
   <string name="share">&share;</string>
   <string name="share_title">&share_title;</string>
   <string name="share_image_failed">&share_image_failed;</string>
   <string name="save_as_pdf">&save_as_pdf;</string>
   <string name="print">&print;</string>
   <string name="find_in_page">&find_in_page;</string>
-  <string name="find_matchcase">&find_matchcase;</string>
   <string name="desktop_mode">&desktop_mode;</string>
   <string name="page">&page;</string>
   <string name="tools">&tools;</string>
 
   <string name="find_text">&find_text;</string>
   <string name="find_prev">&find_prev;</string>
   <string name="find_next">&find_next;</string>
   <string name="find_close">&find_close;</string>
--- a/mobile/android/chrome/content/FindHelper.js
+++ b/mobile/android/chrome/content/FindHelper.js
@@ -35,28 +35,28 @@ var FindHelper = {
     try {
       this._limit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
     } catch (e) {
       // Pref not available, assume 0, no match counting.
       this._limit = 0;
     }
 
     Messaging.addListener((data) => {
-      this.doFind(data.searchString, data.matchCase);
-      return this._getMatchesCountResult(data.searchString);
+      this.doFind(data);
+      return this._getMatchesCountResult(data);
     }, "FindInPage:Find");
 
     Messaging.addListener((data) => {
-      this.findAgain(data.searchString, false, data.matchCase);
-      return this._getMatchesCountResult(data.searchString);
+      this.findAgain(data, false);
+      return this._getMatchesCountResult(data);
     }, "FindInPage:Next");
 
     Messaging.addListener((data) => {
-      this.findAgain(data.searchString, true, data.matchCase);
-      return this._getMatchesCountResult(data.searchString);
+      this.findAgain(data, true);
+      return this._getMatchesCountResult(data);
     }, "FindInPage:Prev");
   },
 
   _init: function() {
     // If there's no find in progress, start one.
     if (this._finder) {
       return;
     }
@@ -111,35 +111,33 @@ var FindHelper = {
   /**
    * Pass along the count results to FindInPageBar for display.
    */
   onMatchesCountResult: function(result) {
     this._result = result;
     this._result.limit = this._limit;
   },
 
-  doFind: function(searchString, matchCase) {
+  doFind: function(searchString) {
     if (!this._finder) {
       this._init();
     }
 
-    this._finder.caseSensitive = matchCase;
     this._finder.fastFind(searchString, false);
   },
 
-  findAgain: function(searchString, findBackwards, matchCase) {
+  findAgain: function(searchString, findBackwards) {
     // This always happens if the user taps next/previous after re-opening the
     // search bar, and not only forces _init() but also an initial fastFind(STRING)
     // before any findAgain(DIRECTION).
     if (!this._finder) {
-      this.doFind(searchString, matchCase);
+      this.doFind(searchString);
       return;
     }
 
-    this._finder.caseSensitive = matchCase;
     this._finder.findAgain(findBackwards, false, false);
   },
 
   onFindResult: function(aData) {
     if (aData.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
       if (this._viewportChanged) {
         if (this._targetTab != BrowserApp.selectedTab) {
           // this should never happen
--- a/mobile/android/chrome/content/aboutLogins.js
+++ b/mobile/android/chrome/content/aboutLogins.js
@@ -35,21 +35,16 @@ function copyStringShowSnackbar(string, 
   } catch (e) {
     debug("Error copying from about:logins");
     Snackbars.show(gStringBundle.GetStringFromName("loginsDetails.copyFailed"), Snackbars.LENGTH_SHORT);
   }
 }
 
 // Delay filtering while typing in MS
 const FILTER_DELAY = 500;
-/* Constants for usage telemetry */
-const LOGINS_LIST_VIEWED = 0;
-const LOGIN_VIEWED = 1;
-const LOGIN_EDITED = 2;
-const LOGIN_PW_TOGGLED = 3;
 
 var Logins = {
   _logins: [],
   _filterTimer: null,
   _selectedLogin: null,
 
   // Load the logins list, displaying interstitial UI (see
   // #logins-list-loading-body) while loading.  There are careful
@@ -66,19 +61,17 @@ var Logins = {
     let showSpinner = () => {
       this._toggleListBody(true);
       emptyBody.classList.add("hidden");
     };
 
     let getAllLogins = () => {
       let logins = [];
       try {
-        TelemetryStopwatch.start("PWMGR_ABOUT_LOGINS_GET_ALL_LOGINS_MS");
         logins = Services.logins.getAllLogins();
-        TelemetryStopwatch.finish("PWMGR_ABOUT_LOGINS_GET_ALL_LOGINS_MS");
       } catch(e) {
         // It's likely that the Master Password was not entered; give
         // a hint to the next person.
         throw new Error("Possible Master Password permissions error: " + e.toString());
       }
 
       logins.sort((a, b) => a.hostname.localeCompare(b.hostname));
 
@@ -221,17 +214,16 @@ var Logins = {
       let item = this._createItemForLogin(login);
       newList.appendChild(item);
     });
 
     list.parentNode.replaceChild(newList, list);
   },
 
   _showList: function () {
-    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGINS_LIST_VIEWED);
     let loginsListPage = document.getElementById("logins-list-page");
     loginsListPage.classList.remove("hidden");
 
     let editLoginPage = document.getElementById("edit-login-page");
     editLoginPage.classList.add("hidden");
 
     // If the Show/Hide password button has been flipped, reset it
     if (this._isPasswordBtnInHideMode()) {
@@ -244,17 +236,16 @@ var Logins = {
     if (event.state) {
       this._showEditLoginDialog(event.state.id);
     } else {
       this._selectedLogin = null;
       this._showList();
     }
   },
   _showEditLoginDialog: function (login) {
-    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_VIEWED);
     let listPage = document.getElementById("logins-list-page");
     listPage.classList.add("hidden");
 
     let editLoginPage = document.getElementById("edit-login-page");
     editLoginPage.classList.remove("hidden");
 
     let usernameField = document.getElementById("username");
     usernameField.value = login.username;
@@ -284,17 +275,16 @@ var Logins = {
       } else if ((newPassword !== "") && (updateBtn.disabled === true)) {
         updateBtn.disabled = false;
         updateBtn.classList.remove("disabled-btn");
       }
     }, false);
   },
 
   _onSaveEditLogin: function() {
-    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_EDITED);
     let newUsername = document.getElementById("username").value;
     let newPassword = document.getElementById("password").value;
     let newDomain  = document.getElementById("hostname").value;
     let origUsername = this._selectedLogin.username;
     let origPassword = this._selectedLogin.password;
     let origDomain = this._selectedLogin.hostname;
 
     try {
@@ -323,17 +313,16 @@ var Logins = {
       Snackbars.show(gStringBundle.GetStringFromName("editLogin.couldNotSave"), Snackbars.LENGTH_SHORT);
       return;
     }
     Snackbars.show(gStringBundle.GetStringFromName("editLogin.saved1"), Snackbars.LENGTH_SHORT);
     this._showList();
   },
 
   _onPasswordBtn: function () {
-    Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_PW_TOGGLED);
     this._updatePasswordBtn(this._isPasswordBtnInHideMode());
   },
 
   _updatePasswordBtn: function (aShouldShow) {
     let passwordField = document.getElementById("password");
     let button = document.getElementById("password-btn");
     let show = gStringBundle.GetStringFromName("password-btn.show");
     let hide = gStringBundle.GetStringFromName("password-btn.hide");
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
+++ b/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
@@ -22,17 +22,16 @@ import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.ProtocolException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.MissingResourceException;
-import java.util.UUID;
 import java.util.zip.CRC32;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -72,32 +71,37 @@ public class SwitchBoard {
 	
 	public static final String ACTION_CONFIG_FETCHED = ".SwitchBoard.CONFIG_FETCHED";
 
 	private static final String kUpdateServerUrl = "updateServerUrl";
 	private static final String kConfigServerUrl = "configServerUrl";
 	
 	private static final String IS_EXPERIMENT_ACTIVE = "isActive";
 	private static final String EXPERIMENT_VALUES = "values";
+
+	private static String uuidExtra = null;
 	
 	
 	/**
 	 * Basic initialization with one server. 
 	 * @param configServerUpdateUrl Url to: http://staging.domain/path_to/SwitchboardURLs.php 
 	 * @param configServerUrl Url to: http://staging.domain/path_to/SwitchboardDriver.php - the acutall config
 	 * @param isDebug Is the application running in debug mode. This will add log messages.
 	 */
 	public static void initDefaultServerUrls(String configServerUpdateUrl, String configServerUrl,
 			boolean isDebug) {
 		
 		DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrl;
 		DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrl;
 		DEBUG = isDebug;
 	}
 	
+	public static void setUUIDFromExtra(String uuid) {
+		uuidExtra = uuid;
+	}
 	/**
 	 * Advanced initialization that supports a production and staging environment without changing the server URLs manually.
 	 * SwitchBoard will connect to the staging environment in debug mode. This makes it very simple to test new experiements
 	 * during development.
 	 * @param configServerUpdateUrlStaging Url to http://staging.domain/path_to/SwitchboardURLs.php in staging environment
 	 * @param configServerUrlStaging Url to: http://staging.domain/path_to/SwitchboardDriver.php in production - the acutall config
 	 * @param configServerUpdateUrl Url to http://staging.domain/path_to/SwitchboardURLs.php in production environment
 	 * @param configServerUrl Url to: http://staging.domain/path_to/SwitchboardDriver.php in production - the acutall config
@@ -430,17 +434,20 @@ public class SwitchBoard {
 		return null;
 	}
 
 	/**
 	 * Return the bucket number of the user. There are 100 possible buckets.
 	 */
 	private static int getUserBucket(Context c) {
 		//get uuid
-		DeviceUuidFactory df = new DeviceUuidFactory(c);
-		String uuid = df.getDeviceUuid().toString();
+		String uuid = uuidExtra;
+		if (uuid == null) {
+			DeviceUuidFactory df = new DeviceUuidFactory(c);
+			uuid = df.getDeviceUuid().toString();
+		}
 
 		CRC32 crc = new CRC32();
 		crc.update(uuid.getBytes());
 		long checksum = crc.getValue();
 		return (int)(checksum % 100L);
 	}
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -915,19 +915,18 @@ pref("devtools.gcli.imgurClientID", '0df
 pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image");
 
 // GCLI commands directory
 pref("devtools.commands.dir", "");
 
 // Allows setting the performance marks for which telemetry metrics will be recorded.
 pref("devtools.telemetry.supported_performance_marks", "contentInteractive,navigationInteractive,navigationLoaded,visuallyLoaded,fullyLoaded,mediaEnumerated,scanEnd");
 
-// Deprecation warnings after DevTools file migration.  Bug 1204127 tracks
-// enabling this.
-pref("devtools.migration.warnings", false);
+// Deprecation warnings after DevTools file migration.
+pref("devtools.migration.warnings", true);
 
 // view source
 pref("view_source.syntax_highlight", true);
 pref("view_source.wrap_long_lines", false);
 pref("view_source.editor.external", false);
 pref("view_source.editor.path", "");
 // allows to add further arguments to the editor; use the %LINE% placeholder
 // for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
@@ -5191,9 +5190,8 @@ pref("toolkit.pageThumbs.minHeight", 0);
 
 pref("webextensions.tests", false);
 
 // Allow customization of the fallback directory for file uploads
 pref("dom.input.fallbackUploadDir", "");
 
 // Turn rewriting of youtube embeds on/off
 pref("plugins.rewrite_youtube_embeds", true);
-
--- a/testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
@@ -32,16 +32,17 @@ const SCRIPTS = [
   "browser/base/content/browser-devedition.js",
   "browser/base/content/browser-eme.js",
   "browser/base/content/browser-feeds.js",
   "browser/base/content/browser-fullScreen.js",
   "browser/base/content/browser-fullZoom.js",
   "browser/base/content/browser-gestureSupport.js",
   "browser/base/content/browser-places.js",
   "browser/base/content/browser-plugins.js",
+  "browser/base/content/browser-refreshblocker.js",
   "browser/base/content/browser-safebrowsing.js",
   "browser/base/content/browser-sidebar.js",
   "browser/base/content/browser-social.js",
   "browser/base/content/browser-syncui.js",
   "browser/base/content/browser-tabsintitlebar.js",
   "browser/base/content/browser-thumbnails.js",
   "browser/base/content/browser-trackingprotection.js",
   "browser/base/content/browser-data-submission-info-bar.js",
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -789,9 +789,41 @@ this.BrowserTestUtils = {
         if (conditionPassed) {
           clearInterval(intervalID);
           resolve();
         }
         tries++;
       }, interval);
     });
   },
+
+  /**
+   * Waits for a <xul:notification> with a particular value to appear
+   * for the <xul:notificationbox> of the passed in browser.
+   *
+   * @param tabbrowser (<xul:tabbrowser>)
+   *        The gBrowser that hosts the browser that should show
+   *        the notification. For most tests, this will probably be
+   *        gBrowser.
+   * @param browser (<xul:browser>)
+   *        The browser that should be showing the notification.
+   * @param notificationValue (string)
+   *        The "value" of the notification, which is often used as
+   *        a unique identifier. Example: "plugin-crashed".
+   * @return Promise
+   *        Resolves to the <xul:notification> that is being shown.
+   */
+  waitForNotificationBar(tabbrowser, browser, notificationValue) {
+    let notificationBox = tabbrowser.getNotificationBox(browser);
+    return new Promise((resolve) => {
+      let check = (event) => {
+        return event.target.value == notificationValue;
+      };
+
+      BrowserTestUtils.waitForEvent(notificationBox, "AlertActive",
+                                    false, check).then((event) => {
+        // The originalTarget of the AlertActive on a notificationbox
+        // will be the notification itself.
+        resolve(event.originalTarget);
+      });
+    });
+  },
 };
--- a/toolkit/.eslintrc
+++ b/toolkit/.eslintrc
@@ -105,17 +105,17 @@
 
     // No reassigning native JS objects
     "no-native-reassign": 2,
 
     // No (!foo in bar)
     "no-negated-in-lhs": 2,
 
     // Nested ternary statements are confusing
-    // "no-nested-ternary": 2,
+    "no-nested-ternary": 2,
 
     // Use {} instead of new Object()
     // "no-new-object": 2,
 
     // No Math() or JSON()
     "no-obj-calls": 2,
 
     // No octal literals
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -1188,19 +1188,17 @@ TreeNode.compareAmounts = function(aA, a
   }
   if (a < b) {
     return 1;
   }
   return TreeNode.compareUnsafeNames(aA, aB);
 };
 
 TreeNode.compareUnsafeNames = function(aA, aB) {
-  return aA._unsafeName < aB._unsafeName ? -1 :
-         aA._unsafeName > aB._unsafeName ?  1 :
-         0;
+  return aA._unsafeName.localeCompare(aB._unsafeName);
 };
 
 
 /**
  * Fill in the remaining properties for the specified tree in a bottom-up
  * fashion.
  *
  * @param aRoot
--- a/toolkit/components/microformats/test/static/javascript/chai.js
+++ b/toolkit/components/microformats/test/static/javascript/chai.js
@@ -1588,22 +1588,23 @@ module.exports = function (chai, _) {
         flag(this, 'object', err);
         return this;
       } else {
         thrown = true;
         thrownError = err;
       }
     }
 
-    var actuallyGot = ''
-      , expectedThrown = name !== null
-        ? name
-        : desiredError
-          ? '#{exp}' //_.inspect(desiredError)
-          : 'an error';
+    var actuallyGot = '';
+    var expectedThrown = 'an error';
+    if (name !== null) {
+      expectedThrown = name;
+    } else if (desiredError) {
+      expectedThrown = '#{exp}'; //_.inspect(desiredError)
+    }
 
     if (thrown) {
       actuallyGot = ' but #{act} was thrown'
     }
 
     this.assert(
         thrown === true
       , 'expected #{this} to throw ' + expectedThrown + actuallyGot
--- a/toolkit/components/microformats/test/static/javascript/mocha.js
+++ b/toolkit/components/microformats/test/static/javascript/mocha.js
@@ -410,17 +410,23 @@ var JsDiff = (function() {
       return ret.join('');
     },
 
     // See: http://code.google.com/p/google-diff-match-patch/wiki/API
     convertChangesToDMP: function(changes){
       var ret = [], change;
       for ( var i = 0; i < changes.length; i++) {
         change = changes[i];
-        ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
+        var order = 0;
+        if (change.added) {
+          order = 1;
+        } else if (change.removed) {
+          order = -1;
+        }
+        ret.push([order, change.value]);
       }
       return ret;
     }
   };
 })();
 
 if (typeof module !== 'undefined') {
     module.exports = JsDiff;
@@ -2092,22 +2098,23 @@ var color = exports.color = function(typ
 };
 
 /**
  * Expose term window size, with some
  * defaults for when stderr is not a tty.
  */
 
 exports.window = {
-  width: isatty
-    ? process.stdout.getWindowSize
-      ? process.stdout.getWindowSize(1)[0]
-      : tty.getWindowSize()[1]
-    : 75
+  width: 75
 };
+if (isatty) {
+  exports.window.width = process.stdout.getWindowSize
+                       ? process.stdout.getWindowSize(1)[0]
+                       : tty.getWindowSize()[1];
+}
 
 /**
  * Expose some basic cursor interactions
  * that are common among reporters.
  */
 
 exports.cursor = {
   hide: function(){
@@ -2235,21 +2242,23 @@ function Base(runner) {
     stats.tests = stats.tests || 0;
     stats.tests++;
   });
 
   runner.on('pass', function(test){
     stats.passes = stats.passes || 0;
 
     var medium = test.slow() / 2;
-    test.speed = test.duration > test.slow()
-      ? 'slow'
-      : test.duration > medium
-        ? 'medium'
-        : 'fast';
+    if (test.duration > test.slow()) {
+      test.speed = 'slow';
+    } else if (test.duration > medium) {
+      test.speed = 'medium';
+    } else {
+      test.speed = 'fast';
+    }
 
     stats.passes++;
   });
 
   runner.on('fail', function(test, err){
     stats.failures = stats.failures || 0;
     stats.failures++;
     test.err = err;
--- a/toolkit/components/microformats/test/static/javascript/prettify.js
+++ b/toolkit/components/microformats/test/static/javascript/prettify.js
@@ -576,21 +576,21 @@ var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?
    * content, but not to return anything where there are multiple child elements
    * as in {@code <pre><code>...</code><code>...</code></pre>} or when there
    * is textual content.
    */
   function childContentWrapper(element) {
     var wrapper = undefined;
     for (var c = element.firstChild; c; c = c.nextSibling) {
       var type = c.nodeType;
-      wrapper = (type === 1)  // Element Node
-          ? (wrapper ? element : c)
-          : (type === 3)  // Text Node
-          ? (notWs.test(c.nodeValue) ? element : wrapper)
-          : wrapper;
+      if (type === 1) {
+        wrapper = wrapper ? element : c;
+      } else if (type === 3) {
+        wrapper = notWs.test(c.nodeValue) ? element : wrapper;
+      }
     }
     return wrapper === element ? undefined : wrapper;
   }
 
   /** Given triples of [style, pattern, context] returns a lexing function,
     * The lexing function interprets the patterns to find token boundaries and
     * returns a decoration list of the form
     * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
@@ -1406,19 +1406,21 @@ var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?
               nested = true;
               break;
             }
           }
           if (!nested) {
             // Look for a class like linenums or linenums:<n> where <n> is the
             // 1-indexed number of the first line.
             var lineNums = cs.className.match(/\blinenums\b(?::(\d+))?/);
-            lineNums = lineNums
-                  ? lineNums[1] && lineNums[1].length ? +lineNums[1] : true
-                  : false;
+            if (lineNums) {
+              lineNums = lineNums[1] && lineNums[1].length ? +lineNums[1] : true;
+            } else {
+              lineNums = false;
+            }
             if (lineNums) { numberLines(cs, lineNums); }
 
             // do the pretty printing
             prettyPrintingJob = {
               langExtension: langExtension,
               sourceNode: cs,
               numberLines: lineNums
             };
--- a/toolkit/components/perfmonitoring/AddonWatcher.jsm
+++ b/toolkit/components/perfmonitoring/AddonWatcher.jsm
@@ -11,17 +11,17 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "console",
-                                  "resource://gre/modules/devtools/Console.jsm");
+                                  "resource://gre/modules/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PerformanceWatcher",
                                   "resource://gre/modules/PerformanceWatcher.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                   "@mozilla.org/base/telemetry;1",
                                   Ci.nsITelemetry);
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "IdleService",
--- a/toolkit/components/places/BookmarkJSONUtils.jsm
+++ b/toolkit/components/places/BookmarkJSONUtils.jsm
@@ -251,18 +251,21 @@ BookmarkImporter.prototype = {
       PlacesUtils.unwrapNodes(aString, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
 
     if (nodes.length == 0 || !nodes[0].children ||
         nodes[0].children.length == 0) {
       deferred.resolve(); // Nothing to restore
     } else {
       // Ensure tag folder gets processed last
       nodes[0].children.sort(function sortRoots(aNode, bNode) {
-        return (aNode.root && aNode.root == "tagsFolder") ? 1 :
-               (bNode.root && bNode.root == "tagsFolder") ? -1 : 0;
+        if (aNode.root && aNode.root == "tagsFolder")
+          return 1;
+        if (bNode.root && bNode.root == "tagsFolder")
+          return -1;
+        return 0;
       });
 
       let batch = {
         nodes: nodes[0].children,
         runBatched: function runBatched() {
           if (this._replace) {
             // Get roots excluded from the backup, we will not remove them
             // before restoring.
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -972,18 +972,19 @@ function reorderChildren(parent, ordered
         return undefined;
 
       // Reorder the children array according to the specified order, provided
       // GUIDs come first, others are appended in somehow random order.
       children.sort((a, b) => {
         let i = orderedChildrenGuids.indexOf(a.guid);
         let j = orderedChildrenGuids.indexOf(b.guid);
         // This works provided fetchBookmarksByParent returns sorted children.
-        return (i == -1 && j == -1) ? 0 :
-                 (i != -1 && j != -1 && i < j) || (i != -1 && j == -1) ? -1 : 1;
+        if (i == -1 && j == -1)
+          return 0;
+        return (i != -1 && j != -1 && i < j) || (i != -1 && j == -1) ? -1 : 1;
        });
 
       // Update the bookmarks position now.  If any unknown guid have been
       // inserted meanwhile, its position will be set to -position, and we'll
       // handle it later.
       // To do the update in a single step, we build a VALUES (guid, position)
       // table.  We then use count() in the sorting table to avoid skipping values
       // when no more existing GUIDs have been provided.
--- a/toolkit/components/places/PlacesBackups.jsm
+++ b/toolkit/components/places/PlacesBackups.jsm
@@ -168,17 +168,17 @@ this.PlacesBackups = {
           continue;
         }
         this._entries.push(entry);
       }
     }
     this._entries.sort((a, b) => {
       let aDate = this.getDateForFile(a);
       let bDate = this.getDateForFile(b);
-      return aDate < bDate ? 1 : aDate > bDate ? -1 : 0;
+      return bDate - aDate;
     });
     return this._entries;
   },
 
   /**
    * Cache current backups in a sorted (by date DESC) array.
    * @return {Promise}
    * @resolve a sorted array of string paths.
@@ -210,17 +210,17 @@ this.PlacesBackups = {
           }
         }
       }.bind(this));
       iterator.close();
 
       this._backupFiles.sort((a, b) => {
         let aDate = this.getDateForFile(a);
         let bDate = this.getDateForFile(b);
-        return aDate < bDate ? 1 : aDate > bDate ? -1 : 0;
+        return bDate - aDate;
       });
 
       return this._backupFiles;
     }.bind(this));
   },
 
   /**
    * Generates a ISO date string (YYYY-MM-DD) from a Date object.
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -1746,26 +1746,31 @@ Search.prototype = {
    *
    * @return an array consisting of the correctly optimized query to search the
    *         database with and an object containing the params to bound.
    */
   get _hostQuery() {
     let typed = Prefs.autofillTyped || this.hasBehavior("typed");
     let bookmarked = this.hasBehavior("bookmark") && !this.hasBehavior("history");
 
-    return [
-      bookmarked ? typed ? SQL_BOOKMARKED_TYPED_HOST_QUERY
-                         : SQL_BOOKMARKED_HOST_QUERY
-                 : typed ? SQL_TYPED_HOST_QUERY
-                         : SQL_HOST_QUERY,
-      {
-        query_type: QUERYTYPE_AUTOFILL_HOST,
-        searchString: this._searchString.toLowerCase()
-      }
-    ];
+    let query = [];
+    if (bookmarked) {
+      query.push(typed ? SQL_BOOKMARKED_TYPED_HOST_QUERY
+                       : SQL_BOOKMARKED_HOST_QUERY);
+    } else {
+      query.push(typed ? SQL_TYPED_HOST_QUERY
+                       : SQL_HOST_QUERY);
+    }
+
+    query.push({
+      query_type: QUERYTYPE_AUTOFILL_HOST,
+      searchString: this._searchString.toLowerCase()
+    });
+
+    return query;
   },
 
   /**
    * Obtains the query to search for autoFill url results.
    *
    * @return an array consisting of the correctly optimized query to search the
    *         database with and an object containing the params to bound.
    */
@@ -1775,27 +1780,32 @@ Search.prototype = {
     // query.
     let slashIndex = this._autofillUrlSearchString.indexOf("/");
     let revHost = this._autofillUrlSearchString.substring(0, slashIndex).toLowerCase()
                       .split("").reverse().join("") + ".";
 
     let typed = Prefs.autofillTyped || this.hasBehavior("typed");
     let bookmarked = this.hasBehavior("bookmark") && !this.hasBehavior("history");
 
-    return [
-      bookmarked ? typed ? SQL_BOOKMARKED_TYPED_URL_QUERY
-                         : SQL_BOOKMARKED_URL_QUERY
-                 : typed ? SQL_TYPED_URL_QUERY
-                         : SQL_URL_QUERY,
-      {
-        query_type: QUERYTYPE_AUTOFILL_URL,
-        searchString: this._autofillUrlSearchString,
-        revHost
-      }
-    ];
+    let query = [];
+    if (bookmarked) {
+      query.push(typed ? SQL_BOOKMARKED_TYPED_URL_QUERY
+                       : SQL_BOOKMARKED_URL_QUERY);
+    } else {
+      query.push(typed ? SQL_TYPED_URL_QUERY
+                       : SQL_URL_QUERY);
+    }
+
+    query.push({
+      query_type: QUERYTYPE_AUTOFILL_URL,
+      searchString: this._autofillUrlSearchString,
+      revHost
+    });
+
+    return query;
   },
 
  /**
    * Notifies the listener about results.
    *
    * @param searchOngoing
    *        Indicates whether the search is ongoing.
    */
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -529,19 +529,22 @@ function check_JSON_backup(aIsAutomaticB
  * Returns the frecency of a url.
  *
  * @param aURI
  *        The URI or spec to get frecency for.
  * @return the frecency value.
  */
 function frecencyForUrl(aURI)
 {
-  let url = aURI instanceof Ci.nsIURI ? aURI.spec
-                                      : aURI instanceof URL ? aURI.href
-                                                            : aURI;
+  let url = aURI;
+  if (aURI instanceof Ci.nsIURI) {
+    url = aURI.spec;
+  } else if (aURI instanceof URL) {
+    url = aURI.href;
+  }
   let stmt = DBConn().createStatement(
     "SELECT frecency FROM moz_places WHERE url = ?1"
   );
   stmt.bindByIndex(0, url);
   try {
     if (!stmt.executeStep()) {
       throw new Error("No result for frecency.");
     }
--- a/toolkit/components/reader/Readability.js
+++ b/toolkit/components/reader/Readability.js
@@ -67,18 +67,22 @@ var Readability = function(uri, doc, opt
   // Control whether log messages are sent to the console
   if (this._debug) {
     function logEl(e) {
       var rv = e.nodeName + " ";
       if (e.nodeType == e.TEXT_NODE) {
         return rv + '("' + e.textContent + '")';
       }
       var classDesc = e.className && ("." + e.className.replace(/ /g, "."));
-      var elDesc = e.id ? "(#" + e.id + classDesc + ")" :
-                          (classDesc ? "(" + classDesc + ")" : "");
+      var elDesc = "";
+      if (e.id) {
+        elDesc = "(#" + e.id + classDesc + ")";
+      } else if (classDesc) {
+        elDesc = "(" + classDesc + ")";
+      }
       return rv + elDesc;
     }
     this.log = function () {
       if ("dump" in root) {
         var msg = Array.prototype.map.call(arguments, function(x) {
           return (x && x.nodeName) ? logEl(x) : x;
         }).join(" ");
         dump("Reader: (Readability) " + msg + "\n");
@@ -738,17 +742,17 @@ Readability.prototype = {
             this._initializeNode(ancestor);
             candidates.push(ancestor);
           }
 
           // Node score divider:
           // - parent:             1 (no division)
           // - grandparent:        2
           // - great grandparent+: ancestor level * 3
-          var scoreDivider = level === 0 ? 1 : level === 1 ? 2 : level * 3;
+          var scoreDivider = level < 2 ? level + 1 : level * 3;
           ancestor.readability.contentScore += contentScore / scoreDivider;
         });
       });
 
       // After we've calculated scores, loop through all of the possible
       // candidate nodes we found and find the one with the highest score.
       var topCandidates = [];
       for (var c = 0, cl = candidates.length; c < cl; c += 1) {
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5151,16 +5151,23 @@
     "expires_in_version": "never",
     "kind": "exponential",
     "low": 10,
     "high": "20000",
     "n_buckets": 20,
     "description": "Time for the home screen Top Sites query to return with no filter set (ms)",
     "cpp_guard": "ANDROID"
   },
+  "FENNEC_HOMEPANELS_CUSTOM": {
+    "expires_in_version": "54",
+    "kind": "boolean",
+    "bug_numbers": [1245368],
+    "description": "Whether the user has customized their homepanels",
+    "cpp_guard": "ANDROID"
+  },
   "FENNEC_WAS_KILLED": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "Killed, likely due to an OOM condition",
     "cpp_guard": "ANDROID"
   },
   "FIPS_ENABLED": {
     "alert_emails": ["seceng@mozilla.org"],
@@ -8714,29 +8721,16 @@
   "FX_SANITIZE_OPENWINDOWS": {
     "alert_emails": ["firefox-dev@mozilla.org", "gavin@mozilla.com"],
     "expires_in_version": "50",
     "kind": "exponential",
     "high": "30000",
     "n_buckets": 20,
     "description": "Sanitize: Time it takes to sanitize the open windows list (ms)"
   },
-  "PWMGR_ABOUT_LOGINS_GET_ALL_LOGINS_MS": {
-    "expires_in_version": "55",
-    "kind": "exponential",
-    "high": 60000,
-    "n_buckets": 30,
-    "description": "How long getAllLogins() on about:logins takes for mobile users"
-  },
-  "PWMGR_ABOUT_LOGINS_USAGE": {
-    "expires_in_version": "55",
-    "kind": "enumerated",
-    "n_values": 12,
-    "description": "Usage of about:logins 0= list of logins viewed, 1=a login's specifics page was viewed, 2=user edited login credentials 3=user toggled the show/hide button"
-  },
   "PWMGR_BLOCKLIST_NUM_SITES": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 100,
     "n_buckets" : 10,
     "description": "The number of sites for which the user has explicitly rejected saving logins"
   },
   "PWMGR_FORM_ACTION_EFFECT": {
@@ -9026,21 +9020,29 @@
     "expires_in_version": "50",
     "alert_emails": ["mleibovic@mozilla.com"],
     "kind": "enumerated",
     "n_values": 5,
     "description": "The result of trying to download a document to show in reader view (0=Success, 1=Error XHR, 2=Error no document)"
   },
   "FENNEC_READER_VIEW_BUTTON" : {
     "expires_in_version": "50",
-    "alert_emails": ["mleibovic@mozilla.com"],
+    "alert_emails": ["mobile-frontend@mozilla.com"],
     "kind": "enumerated",
     "n_values": 10,
     "description": "Bug 1219240: Measures user interaction with the reader view button (0=Button hidden, 1=Button shown, 2=Tap to enter reader view, 3=Tap to exit reader view, 4=Long tap)"
   },
+  "FENNEC_LOAD_SAVED_PAGE": {
+    "expires_in_version": "50",
+    "alert_emails": ["mobile-frontend@mozilla.com"],
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "How often users load saved items when online/offline (0=RL online, 1=RL offline, 2=BM online, 3=BM offline)",
+    "bug_numbers": [1243387]
+  },
   "PERMISSIONS_SQL_CORRUPTED": {
     "expires_in_version": "never",
     "kind": "count",
     "description": "Record the permissions.sqlite init failure"
   },
   "DEFECTIVE_PERMISSIONS_SQL_REMOVED": {
     "expires_in_version": "never",
     "kind": "count",
--- a/toolkit/components/telemetry/docs/main-ping.rst
+++ b/toolkit/components/telemetry/docs/main-ping.rst
@@ -78,16 +78,20 @@ If the monotonic clock failed, this will
 
 subsessionLength
 ~~~~~~~~~~~~~~~~
 The length of this subsession in seconds.
 This uses a monotonic clock, so this may mismatch with other measurements that are not monotonic (e.g. based on Date.now()).
 
 If ``sessionLength`` is ``-1``, the monotonic clock is not working.
 
+childPayloads
+-------------
+The Telemetry payloads sent by child processes. They are reduced session payloads, only available with e10s. Among some other things, they don't report addon details, addon histograms or UI Telemetry.
+
 simpleMeasurements
 ------------------
 This section contains a list of simple measurements, or counters. In addition to the ones highlighted below, Telemetry timestamps (see `here <https://dxr.mozilla.org/mozilla-central/search?q=%22TelemetryTimestamps.add%22&redirect=false&case=true>`_ and `here <https://dxr.mozilla.org/mozilla-central/search?q=%22recordTimestamp%22&redirect=false&case=true>`_) can be reported.
 
 totalTime
 ~~~~~~~~~
 A non-monotonic integer representing the number of seconds the session has been alive.
 
@@ -159,16 +163,24 @@ activeTicks
 Integer count of the number of five-second intervals ('ticks') the user was considered 'active' (sending UI events to the window). An extra event is fired immediately when the user becomes active after being inactive. This is for some mouse and gamepad events, and all touch, keyboard, wheel, and pointer events (see `EventStateManager.cpp <https://dxr.mozilla.org/mozilla-central/rev/e6463ae7eda2775bc84593bb4a0742940bb87379/dom/events/EventStateManager.cpp#549>`_).
 This measure might be useful to give a trend of how much a user actually interacts with the browser when compared to overall session duration. It does not take into account whether or not the window has focus or is in the foreground. Just if it is receiving these interaction events.
 Note that in ``main`` pings, this measure is reset on subsession splits, while in ``saved-session`` pings it covers the whole browser session.
 
 pingsOverdue
 ~~~~~~~~~~~~
 Integer count of pending pings that are overdue.
 
+histograms
+----------
+This section contains the histograms that are valid for the current platform. ``Flag`` and ``count`` histograms are always created and submitted, with their default value being respectively ``false`` and ``0``. Other histogram types (`see here <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe#Choosing_a_Histogram_Type>`_) are not created nor submitted if no data was added to them. The type and format of the reported histograms is described by the ``Histograms.json`` file. Its most recent version is available `here <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Histograms.json>`_. The ``info.revision`` field indicates the revision of the file that describes the reported histograms.
+
+keyedHistograms
+---------------
+This section contains the keyed histograms available for the current platform. Unlike the ``histograms`` section, this section always reports all the keyed histograms, even though they contain no data.
+
 threadHangStats
 ---------------
 Contains the statistics about the hangs in main and background threads. Note that hangs in this section capture the [C++ pseudostack](https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler#Native_stack_vs._Pseudo_stack) and an incomplete JS stack, which is not 100% precise.
 
 To avoid submitting overly large payloads, some limits are applied:
 
 * Identical, adjacent "(chrome script)" or "(content script)" stack entries are collapsed together. If a stack is reduced, the "(reduced stack)" frame marker is added as the oldest frame.
 * The depth of the reported stacks is limited to 11 entries. This value represents the 99.9th percentile of the thread hangs stack depths reported by Telemetry.
@@ -244,16 +256,32 @@ Structure::
             "pluginIsWhitelistedForShumway" : "false",
             ... other annotations as key:value pairs ...
           }
         ],
         ...
       ]
     },
 
+log
+---
+This section contains a log of important or unusual events reported through Telemetry.
+
+Structure::
+
+    "log": [
+      [
+        "Event_ID",
+        3785, // the timestamp (in milliseconds) for the log entry
+        ... other data ...
+      ],
+      ...
+    ]
+
+
 webrtc
 ------
 Contains special statistics gathered by WebRTC releated components.
 
 So far only a bitmask for the ICE candidate type present in a successful or
 failed WebRTC connection is getting reported through C++ code as
 IceCandidatesStats, because the required bitmask is too big to be represented
 in a regular enum histogram. Further this data differentiates between Loop
@@ -282,8 +310,51 @@ Structure::
           },
           "73424": {
             "successCount": 1,
             "failureCount": 5
           }
         }
       }
     },
+
+fileIOReports
+-------------
+Contains the statistics of main-thread I/O recorded during the execution. Only the I/O stats for the XRE and the profile directories are currently reported, neither of them disclosing the full local path.
+
+Structure::
+
+    "fileIOReports": {
+      "{xre}": [
+        totalTime, // Accumulated duration of all operations
+        creates, // Number of create/open operations
+        reads, // Number of read operations
+        writes, // Number of write operations
+        fsyncs, // Number of fsync operations
+        stats, // Number of stat operations
+      ],
+      "{profile}": [ ... ],
+      ...
+    }
+
+lateWrites
+----------
+This sections reports writes to the file system that happen during shutdown.
+
+addonDetails
+------------
+This section contains per-addon telemetry details, as reported by each addon provider.
+
+addonHistograms
+---------------
+This section contains the histogram registered by the addons (`see here <https://dxr.mozilla.org/mozilla-central/rev/584870f1cbc5d060a57e147ce249f736956e2b62/toolkit/components/telemetry/nsITelemetry.idl#303>`_). This section is not present if no addon histogram is available.
+
+UITelemetry
+-----------
+See the ``UITelemetry data format`` documentation.
+
+slowSQL
+-------
+This section contains the informations about the slow SQL queries for both the main and other threads. The execution of an SQL statement is considered slow if it takes 50ms or more on the main thread or 100ms or more on other threads. Slow SQL statements will be automatically trimmed to 1000 characters. This limit doesn't include the ellipsis and database name, that are appended at the end of the stored statement.
+
+slowSQLStartup
+--------------
+This section contains the slow SQL statements gathered at startup (until the "sessionstore-windows-restored" event is fired).
--- a/toolkit/components/timermanager/tests/unit/consumerNotifications.js
+++ b/toolkit/components/timermanager/tests/unit/consumerNotifications.js
@@ -476,18 +476,24 @@ function logTestInfo(aText, aCaller) {
   let caller = aCaller ? aCaller : Components.stack.caller;
   let now = new Date;
   let hh = now.getHours();
   let mm = now.getMinutes();
   let ss = now.getSeconds();
   let ms = now.getMilliseconds();
   let time = (hh < 10 ? "0" + hh : hh) + ":" +
              (mm < 10 ? "0" + mm : mm) + ":" +
-             (ss < 10 ? "0" + ss : ss) + ":" +
-             (ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms);
+             (ss < 10 ? "0" + ss : ss) + ":";
+  if (ms < 10) {
+    time += "00";
+  }
+  else if (ms < 100) {
+    time += "0";
+  }
+  time += ms;
   let msg = time + " | TEST-INFO | " + caller.filename + " | [" + caller.name +
             " : " + caller.lineNumber + "] " + aText;
   do_print(msg);
 }
 
 /**
  * Logs TEST-INFO messages when DEBUG_TEST evaluates to true.
  *
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -1236,17 +1236,20 @@ var Histogram = {
       // - see toolkit/components/telemetry/Telemetry.cpp
       //   (CreateJSTimeHistogram, CreateJSThreadHangStats, CreateJSHangHistogram)
       // - see toolkit/components/telemetry/ThreadHangStats.h
       // Fix BHR labels to the "standard" format for about:telemetry as follows:
       //   - The dummy 0 label+bucket will be filtered before arriving here
       //   - If it's 1 -> manually correct it to 0 (the 0..1 anomaly)
       //   - For the rest, set the label as the bottom value instead of the upper.
       //   --> so we'll end with the following (non dummy) labels: 0, 2, 4, 8, 16, ...
-      return !aIsBHR ? k : k == 1 ? 0 : (k + 1) / 2;
+      if (!aIsBHR) {
+        return k;
+      }
+      return k == 1 ? 0 : (k + 1) / 2;
     }
 
     const labelledValues = Object.keys(aHgram.values)
                            .filter(label => !aIsBHR || Number(label) != 0) // remove dummy 0 label for BHR
                            .map(k => [labelFunc(Number(k)), aHgram.values[k]]);
 
     let result = {
       values: labelledValues,
--- a/toolkit/content/tests/widgets/tree_shared.js
+++ b/toolkit/content/tests/widgets/tree_shared.js
@@ -388,17 +388,21 @@ function testtag_tree_TreeSelection_UI(t
   synthesizeKeyExpectEvent("VK_UP", { accelKey: true }, tree, "!select", "key up with accel");
   testtag_tree_TreeSelection_State(tree, testid + "key up with accel", multiple ? 3 : 4, [4]);
   if (!multiple)
     is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with accel and scroll");
 
   // do this three times, one for each state of pageUpOrDownMovesSelection,
   // and then once with the accel key pressed
   for (let t = 0; t < 3; t++) {
-    let testidmod = (t == 2) ? " with accel" : (t == 1) ? " rev" : "";
+    let testidmod = "";
+    if (t == 2)
+      testidmod = " with accel"
+    else if (t == 1)
+      testidmod = " rev";
     var keymod = (t == 2) ? { accelKey: true } : { };
 
     var moveselection = tree.pageUpOrDownMovesSelection;
     if (t == 2)
       moveselection = !moveselection;
 
     tree.treeBoxObject.scrollToRow(4);
     selection.currentIndex = 6;
@@ -1159,20 +1163,30 @@ function testtag_tree_wheel(aTree)
   const deltaModes = [
     WheelEvent.DOM_DELTA_PIXEL,  // 0
     WheelEvent.DOM_DELTA_LINE,   // 1
     WheelEvent.DOM_DELTA_PAGE    // 2
   ];
   function helper(aStart, aDelta, aIntDelta, aDeltaMode)
   {
     aTree.treeBoxObject.scrollToRow(aStart);
-    var expected = !aIntDelta ? aStart :
-          aDeltaMode != WheelEvent.DOM_DELTA_PAGE ? aStart + aIntDelta :
-          aIntDelta > 0 ? aStart + aTree.treeBoxObject.getPageLength() :
-                          aStart - aTree.treeBoxObject.getPageLength();
+    var expected;
+    if (!aIntDelta) {
+      expected = aStart;
+    }
+    else if (aDeltaMode != WheelEvent.DOM_DELTA_PAGE) {
+      expected = aStart + aIntDelta;
+    }
+    else if (aIntDelta > 0) {
+      expected = aStart + aTree.treeBoxObject.getPageLength();
+    }
+    else {
+      expected = aStart - aTree.treeBoxObject.getPageLength();
+    }
+
     if (expected < 0) {
       expected = 0;
     }
     if (expected > aTree.view.rowCount - aTree.treeBoxObject.getPageLength()) {
       expected = aTree.view.rowCount - aTree.treeBoxObject.getPageLength();
     }
     synthesizeWheel(aTree.body, 1, 1,
                     { deltaMode: aDeltaMode, deltaY: aDelta,
--- a/toolkit/content/widgets/button.xml
+++ b/toolkit/content/widgets/button.xml
@@ -58,18 +58,21 @@
         ]]></setter>
       </property>
 
       <property name="checkState">
         <getter><![CDATA[
           var state = this.getAttribute("checkState");
           if (state == "")
             return this.checked ? 1 : 0;
-          else
-            return state == "0" ? 0 : (state == "2" ? 2 : 1);
+          if (state == "0")
+            return 0;
+          if (state == "2")
+            return 2;
+          return 1;
         ]]></getter>
         <setter><![CDATA[
           this.setAttribute("checkState", val);
           return val;
         ]]></setter>
       </property>
 
       <property name="autoCheck"
--- a/toolkit/content/widgets/datetimepicker.xml
+++ b/toolkit/content/widgets/datetimepicker.xml
@@ -773,34 +773,39 @@
             var dt = new Date(2002,9,4).toLocaleFormat("%x");
             var numberFields = dt.match(numberOrder);
             if (numberFields) {
               this._separatorFirst.value = numberFields[3];
               this._separatorSecond.value = numberFields[5];
 
               var yi = 2, mi = 4, di = 6;
 
+              function fieldForNumber(i) {
+                if (i == 2)
+                  return "input-one";
+                if (i == 4)
+                  return "input-two";
+                return "input-three";
+              }
+
               for (var i = 1; i < numberFields.length; i++) {
                 switch (Number(numberFields[i])) {
                   case 2:
                     twoDigitYear = true; // fall through
                   case 2002:
                     yi = i;
-                    yfield = (i == 2 ? "input-one" :
-                             (i == 4 ? "input-two" : "input-three"));
+                    yfield = fieldForNumber(i);
                     break;
                   case 9, 10:
                     mi = i;
-                    mfield = (i == 2 ? "input-one" :
-                             (i == 4 ? "input-two" : "input-three"));
+                    mfield = fieldForNumber(i);
                     break;
                   case 4:
                     di = i;
-                    dfield = (i == 2 ? "input-one" :
-                             (i == 4 ? "input-two" : "input-three"));
+                    dfield = fieldForNumber(i);
                     break;
                 }
               }
 
               this.yearLeadingZero = (numberFields[yi].length > 1);
               this.monthLeadingZero = (numberFields[mi].length > 1);
               this.dateLeadingZero = (numberFields[di].length > 1);
             }
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -460,20 +460,28 @@
       <![CDATA[
         this.adjustArrowPosition();
         if (this.getAttribute("animate") != "false") {
           this.setAttribute("animate", "open");
         }
 
         // set fading
         var fade = this.getAttribute("fade");
-        var fadeDelay = (fade == "fast") ? 1 : fade == "slow" ? 4000 : 0;
-        if (fadeDelay) {
-          this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
+        var fadeDelay = 0;
+        if (fade == "fast") {
+          fadeDelay = 1;
         }
+        else if (fade == "slow") {
+          fadeDelay = 4000;
+        }
+        else {
+          return;
+        }
+
+        this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
       ]]>
       </handler>
       <handler event="popuphiding" phase="target">
         let animate = (this.getAttribute("animate") != "false");
 
         if (this._fadeTimer) {
           clearTimeout(this._fadeTimer);
           if (animate) {
--- a/toolkit/content/widgets/preferences.xml
+++ b/toolkit/content/widgets/preferences.xml
@@ -438,17 +438,19 @@
               var f = new Function ("event",
                                     aElement.getAttribute("onsyncfrompreference"));
               rv = f.call(aElement, event);
             }
             catch (e) {
               Components.utils.reportError(e);
             }
           }
-          var val = rv !== undefined ? rv : (this.instantApply ? this.valueFromPreferences : this.value);
+          var val = rv;
+          if (val === undefined)
+            val = this.instantApply ? this.valueFromPreferences : this.value;
           // if the preference is marked for reset, show default value in UI
           if (val === undefined)
             val = this.defaultValue;
 
           /**
            * Initialize a UI element property with a value. Handles the case
            * where an element has not yet had a XBL binding attached for it and
            * the property setter does not yet exist by setting the same attribute
--- a/toolkit/content/widgets/tree.xml
+++ b/toolkit/content/widgets/tree.xml
@@ -1232,17 +1232,20 @@
     <implementation>
       <constructor>
         this.parentNode.parentNode._columnsDirty = true;
       </constructor>
 
       <property name="ordinal">
         <getter><![CDATA[
           var val = this.getAttribute("ordinal");
-          return "" + (val == "" ? 1 : (val == "0" ? 0 : parseInt(val)));
+          if (val == "")
+            return "1";
+
+          return "" + (val == "0" ? 0 : parseInt(val));
         ]]></getter>
         <setter><![CDATA[
           this.setAttribute("ordinal", val);
           return val;
         ]]></setter>
       </property>
 
       <property name="_previousVisibleColumn">
--- a/toolkit/modules/Geometry.jsm
+++ b/toolkit/modules/Geometry.jsm
@@ -257,20 +257,28 @@ Rect.prototype = {
     this.top = f.call(this, this.top);
     this.right = f.call(this, this.right);
     this.bottom = f.call(this, this.bottom);
     return this;
   },
 
   /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
   translateInside: function translateInside(other) {
-    let offsetX = (this.left <= other.left ? other.left - this.left :
-                  (this.right > other.right ? other.right - this.right : 0));
-    let offsetY = (this.top <= other.top ? other.top - this.top :
-                  (this.bottom > other.bottom ? other.bottom - this.bottom : 0));
+    let offsetX = 0;
+    if (this.left <= other.left)
+      offsetX = other.left - this.left;
+    else if (this.right > other.right)
+      offsetX = other.right - this.right;
+
+    let offsetY = 0;
+    if (this.top <= other.top)
+      offsetY = other.top - this.top;
+    else if (this.bottom > other.bottom)
+      offsetY = other.bottom - this.bottom;
+
     return this.translate(offsetX, offsetY);
   },
 
   /** Subtract other area from this. Returns array of rects whose union is this-other. */
   subtract: function subtract(other) {
     let r = new Rect(0, 0, 0, 0);
     let result = [];
     other = other.intersect(this);
--- a/toolkit/modules/sessionstore/XPathGenerator.jsm
+++ b/toolkit/modules/sessionstore/XPathGenerator.jsm
@@ -75,19 +75,21 @@ this.XPathGenerator = {
     return /^\w+$/.test(aName) ? aName :
            "*[local-name()=" + this.quoteArgument(aName) + "]";
   },
 
   /**
    * @returns a properly quoted string to insert into an XPath query
    */
   quoteArgument: function sss_xph_quoteArgument(aArg) {
-    return !/'/.test(aArg) ? "'" + aArg + "'" :
-           !/"/.test(aArg) ? '"' + aArg + '"' :
-           "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')";
+    if (!/'/.test(aArg))
+      return "'" + aArg + "'";
+    if (!/"/.test(aArg))
+      return '"' + aArg + '"';
+    return "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')";
   },
 
   /**
    * @returns an XPath query to all savable form field nodes
    */
   get restorableFormNodes() {
     // for a comprehensive list of all available <INPUT> types see
     // http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -315,19 +315,23 @@ function parseRDFManifest(aId, aUpdateKe
   let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
            createInstance(Ci.nsIRDFDataSource);
   rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText);
 
   // Differentiating between add-on types is deprecated
   let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
   let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
   let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
-  let addonRes = ds.ArcLabelsOut(extensionRes).hasMoreElements() ? extensionRes
-               : ds.ArcLabelsOut(themeRes).hasMoreElements() ? themeRes
-               : itemRes;
+  let addonRes;
+  if (ds.ArcLabelsOut(extensionRes).hasMoreElements())
+    addonRes = extensionRes;
+  else if (ds.ArcLabelsOut(themeRes).hasMoreElements())
+    addonRes = themeRes;
+  else
+    addonRes = itemRes;
 
   // If we have an update key then the update manifest must be signed
   if (aUpdateKey) {
     let signature = getProperty(ds, addonRes, "signature");
     if (!signature)
       throw Components.Exception("Update manifest for " + aId + " does not contain a required signature");
     let serializer = new RDFSerializer();
     let updateString = null;
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -328,27 +328,22 @@ function DBAddonInternal(aLoaded) {
     this.location = aLoaded._installLocation.name;
   }
   else if (aLoaded.location) {
     this._installLocation = XPIProvider.installLocationsByName[this.location];
   }
 
   this._key = this.location + ":" + this.id;
 
-  if (aLoaded._sourceBundle) {
-    this._sourceBundle = aLoaded._sourceBundle;
-  }
-  else if (aLoaded.descriptor) {
-    this._sourceBundle = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-    this._sourceBundle.persistentDescriptor = aLoaded.descriptor;
-  }
-  else {
+  if (!aLoaded._sourceBundle) {
     throw new Error("Expected passed argument to contain a descriptor");
   }
 
+  this._sourceBundle = aLoaded._sourceBundle;
+
   XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", function() {
       for (let install of XPIProvider.installs) {
         if (install.state == AddonManager.STATE_INSTALLED &&
             !(install.addon.inDatabase) &&
             install.addon.id == this.id &&
             install.installLocation == this._installLocation) {
           delete this.pendingUpgrade;
           return this.pendingUpgrade = install.addon;
@@ -654,16 +649,26 @@ this.XPIDatabase = {
         // When we rev the schema of the JSON database, we need to make sure we
         // force the DB to save so that the DB_SCHEMA value in the JSON file and
         // the preference are updated.
       }
       // If we got here, we probably have good data
       // Make AddonInternal instances from the loaded data and save them
       let addonDB = new Map();
       for (let loadedAddon of inputAddons.addons) {
+        loadedAddon._sourceBundle = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+        try {
+          loadedAddon._sourceBundle.persistentDescriptor = loadedAddon.descriptor;
+        }
+        catch (e) {
+          // We can fail here when the descriptor is invalid, usually from the
+          // wrong OS
+          logger.warn("Could not find source bundle for add-on " + loadedAddon.id, e);
+        }
+
         let newAddon = new DBAddonInternal(loadedAddon);
         addonDB.set(newAddon._key, newAddon);
       }
       parseTimer.done();
       this.addonDB = addonDB;
       logger.debug("Successfully read XPI database");
       this.initialized = true;
     }
@@ -1878,16 +1883,29 @@ this.XPIDatabaseReconcile = {
     let loadedManifest = (aInstallLocation, aId) => {
       if (!(aInstallLocation.name in aManifests))
         return null;
       if (!(aId in aManifests[aInstallLocation.name]))
         return null;
       return aManifests[aInstallLocation.name][aId];
     };
 
+    // Add-ons loaded from the database can have an uninitialized _sourceBundle
+    // if the descriptor was invalid. Swallow that error and say they don't exist.
+    let exists = (aAddon) => {
+      try {
+        return aAddon._sourceBundle.exists();
+      }
+      catch (e) {
+        if (e.result == Cr.NS_ERROR_NOT_INITIALIZED)
+          return false;
+        throw e;
+      }
+    };
+
     // Get the previous add-ons from the database and put them into maps by location
     let previousAddons = new Map();
     for (let a of XPIDatabase.getAddons()) {
       let locationAddonMap = previousAddons.get(a.location);
       if (!locationAddonMap) {
         locationAddonMap = new Map();
         previousAddons.set(a.location, locationAddonMap);
       }
@@ -2075,17 +2093,17 @@ this.XPIDatabaseReconcile = {
 
           let installReason = Services.vc.compare(previousAddon.version, currentAddon.version) < 0 ?
                               BOOTSTRAP_REASONS.ADDON_UPGRADE :
                               BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
 
           // If the previous add-on was in a different path, bootstrapped
           // and still exists then call its uninstall method.
           if (previousAddon.bootstrap && previousAddon._installLocation &&
-              previousAddon._sourceBundle.exists() &&
+              exists(previousAddon) &&
               currentAddon._sourceBundle.path != previousAddon._sourceBundle.path) {
 
             XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
                                             "uninstall", installReason,
                                             { newVersion: currentAddon.version });
             XPIProvider.unloadBootstrapScope(previousAddon.id);
           }
 
@@ -2134,17 +2152,17 @@ this.XPIDatabaseReconcile = {
     for (let [id, previousAddon] of previousVisible) {
       if (currentVisible.has(id))
         continue;
 
       // This add-on vanished
 
       // If the previous add-on was bootstrapped and still exists then call its
       // uninstall method.
-      if (previousAddon.bootstrap && previousAddon._sourceBundle.exists()) {
+      if (previousAddon.bootstrap && exists(previousAddon)) {
         XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
                                         "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
         XPIProvider.unloadBootstrapScope(previousAddon.id);
       }
       AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id);
 
       // Make sure to flush the cache when an old add-on has gone away
       flushStartupCache();
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_switch_os.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+const ID = "bootstrap1@tests.mozilla.org";
+
+BootstrapMonitor.init();
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+add_task(function*() {
+  startupManager();
+
+  let install = yield new Promise(resolve => AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), resolve));
+  yield promiseCompleteAllInstalls([install]);
+
+  let addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+
+  BootstrapMonitor.checkAddonStarted(ID);
+  do_check_false(addon.userDisabled);
+  do_check_true(addon.isActive);
+
+  yield promiseShutdownManager();
+
+  BootstrapMonitor.checkAddonNotStarted(ID);
+
+  let jData = loadJSON(gExtensionsJSON);
+
+  for (let addon of jData.addons) {
+    if (addon.id == ID) {
+      // Set to something that would be an invalid descriptor for this platform
+      addon.descriptor = AppConstants.platform == "win" ? "/foo/bar" : "C:\\foo\\bar";
+    }
+  }
+
+  saveJSON(jData, gExtensionsJSON);
+
+  startupManager();
+
+  addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+
+  BootstrapMonitor.checkAddonStarted(ID);
+  do_check_false(addon.userDisabled);
+  do_check_true(addon.isActive);
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -301,8 +301,11 @@ run-sequentially = Uses global XCurProcD
 [test_webextension_icons.js]
 [test_webextension.js]
 [test_bootstrap_globals.js]
 [test_bug1180901_2.js]
 skip-if = os != "win"
 [test_bug1180901.js]
 skip-if = os != "win"
 [test_e10s_restartless.js]
+[test_switch_os.js]
+# Bug 1246231
+skip-if = os == "mac" && debug
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -4,17 +4,16 @@ tags = addons
 head = head_addons.js
 tail =
 firefox-appdir = browser
 dupe-manifest =
 support-files =
   data/**
   xpcshell-shared.ini
 
-
 [test_addon_path_service.js]
 [test_asyncBlocklistLoad.js]
 [test_cacheflush.js]
 [test_DeferredSave.js]
 [test_gmpProvider.js]
 skip-if = appname != "firefox"
 [test_hotfix_cert.js]
 [test_isReady.js]
--- a/toolkit/mozapps/update/tests/data/shared.js
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -604,18 +604,23 @@ function logTestInfo(aText, aCaller) {
   let caller = aCaller ? aCaller : Components.stack.caller;
   let now = new Date;
   let hh = now.getHours();
   let mm = now.getMinutes();
   let ss = now.getSeconds();
   let ms = now.getMilliseconds();
   let time = (hh < 10 ? "0" + hh : hh) + ":" +
              (mm < 10 ? "0" + mm : mm) + ":" +
-             (ss < 10 ? "0" + ss : ss) + ":" +
-             (ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms);
+             (ss < 10 ? "0" + ss : ss) + ":";
+  if (ms < 10) {
+    time += "00";
+  } else if (ms < 100) {
+    time += "0";
+  }
+  time += ms;
   let msg = time + " | TEST-INFO | " + caller.filename + " | [" + caller.name +
             " : " + caller.lineNumber + "] " + aText;
   LOG_FUNCTION(msg);
 }
 
 /**
  * Logs TEST-INFO messages when DEBUG_AUS_TEST evaluates to true.
  *