Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 21 Mar 2017 15:32:45 -0700
changeset 348655 18bb0299dd9b3516597e5c68cd49d8b894745a93
parent 348588 9fb5e850ab7ab0b2b90640c604f66038407b411d (current diff)
parent 348654 45468c0cbf032453c1fe65a16b9f18e14cf011a7 (diff)
child 348709 8744e9f8eb99f1290aae81985812d57364f18708
push id31532
push userkwierso@gmail.com
push dateTue, 21 Mar 2017 22:32:51 +0000
treeherdermozilla-central@18bb0299dd9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to central, a=merge MozReview-Commit-ID: 4D4lHKDN9w4
browser/modules/URLBarZoom.jsm
dom/smil/nsSMILMappedAttribute.cpp
dom/smil/nsSMILMappedAttribute.h
layout/reftests/svg/smil/mapped-attr-vs-css-prop-1.svg
layout/style/SVGAttrAnimationRuleProcessor.cpp
layout/style/SVGAttrAnimationRuleProcessor.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -90,16 +90,17 @@ devtools/client/framework/**
 !devtools/client/framework/toolbox*
 devtools/client/inspector/markup/test/doc_markup_events_*.html
 devtools/client/inspector/rules/test/doc_media_queries.html
 devtools/client/memory/test/chrome/*.html
 devtools/client/performance/components/test/test_jit_optimizations_01.html
 devtools/client/projecteditor/**
 devtools/client/responsive.html/test/browser/touch.html
 devtools/client/responsivedesign/**
+!devtools/client/responsivedesign/responsivedesign.jsm
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/*.jsm
 devtools/client/shared/components/reps/reps.js
 devtools/client/shared/components/reps/test/mochitest/*.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
 !devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -23,17 +23,17 @@ Cu.import("resource://gre/modules/Notifi
           LoginManagerParent:false, NewTabUtils:false, PageThumbs:false,
           PluralForm:false, Preferences:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, PromiseUtils:false, ReaderMode:false,
           ReaderParent:false, RecentWindow:false, SessionStore:false,
           ShortcutUtils:false, SimpleServiceDiscovery:false, SitePermissions:false,
           Social:false, TabCrashHandler:false, Task:false, TelemetryStopwatch:false,
           Translation:false, UITour:false, UpdateUtils:false, Weave:false,
           fxAccounts:false, gDevTools:false, gDevToolsBrowser:false, webrtcUI:false,
-          URLBarZoom:false
+          FullZoomUI:false
  */
 
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
 [
   ["AboutHome", "resource:///modules/AboutHome.jsm"],
@@ -44,16 +44,17 @@ Cu.import("resource://gre/modules/Notifi
   ["CastingApps", "resource:///modules/CastingApps.jsm"],
   ["CharsetMenu", "resource://gre/modules/CharsetMenu.jsm"],
   ["Color", "resource://gre/modules/Color.jsm"],
   ["ContentSearch", "resource:///modules/ContentSearch.jsm"],
   ["Deprecated", "resource://gre/modules/Deprecated.jsm"],
   ["E10SUtils", "resource:///modules/E10SUtils.jsm"],
   ["ExtensionsUI", "resource:///modules/ExtensionsUI.jsm"],
   ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+  ["FullZoomUI", "resource:///modules/FullZoomUI.jsm"],
   ["GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm"],
   ["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
   ["Log", "resource://gre/modules/Log.jsm"],
   ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
   ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
   ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
   ["PluralForm", "resource://gre/modules/PluralForm.jsm"],
   ["Preferences", "resource://gre/modules/Preferences.jsm"],
@@ -69,17 +70,16 @@ Cu.import("resource://gre/modules/Notifi
   ["SitePermissions", "resource:///modules/SitePermissions.jsm"],
   ["Social", "resource:///modules/Social.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
   ["TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"],
   ["Translation", "resource:///modules/translation/Translation.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
   ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
-  ["URLBarZoom", "resource:///modules/URLBarZoom.jsm"],
   ["Weave", "resource://services-sync/main.js"],
   ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
   ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
   ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
   ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
 ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
 
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
@@ -1135,17 +1135,17 @@ var gBrowserInit = {
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     FeedHandler.init();
     CompactTheme.init();
     AboutPrivateBrowsingListener.init();
     TrackingProtection.init();
     RefreshBlocker.init();
     CaptivePortalWatcher.init();
-    URLBarZoom.init(window);
+    FullZoomUI.init(window);
 
     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);
 
     // initialize observers and listeners
@@ -6655,25 +6655,31 @@ function checkEmptyPageOrigin(browser = 
   // If another page opened this page with e.g. window.open, this page might
   // be controlled by its opener - return false.
   if (browser.hasContentOpener) {
     return false;
   }
   let contentPrincipal = browser.contentPrincipal;
   // Not all principals have URIs...
   if (contentPrincipal.URI) {
-    // There are two specialcases involving about:blank. One is where
+    // There are two special-cases involving about:blank. One is where
     // the user has manually loaded it and it got created with a null
     // principal. The other involves the case where we load
     // some other empty page in a browser and the current page is the
     // initial about:blank page (which has that as its principal, not
     // just URI in which case it could be web-based). Especially in
     // e10s, we need to tackle that case specifically to avoid race
     // conditions when updating the URL bar.
-    if ((uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
+    //
+    // Note that we check the documentURI here, since the currentURI on
+    // the browser might have been set by SessionStore in order to
+    // support switch-to-tab without having actually loaded the content
+    // yet.
+    let uriToCheck = browser.documentURI || uri;
+    if ((uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
         contentPrincipal.URI.spec == "about:blank") {
       return true;
     }
     return contentPrincipal.URI.equals(uri);
   }
   // ... so for those that don't have them, enforce that the page has the
   // system principal (this matches e.g. on about:newtab).
   let ssm = Services.scriptSecurityManager;
--- a/browser/base/content/sanitize.xul
+++ b/browser/base/content/sanitize.xul
@@ -1,21 +1,18 @@
 <?xml version="1.0"?>
 
-# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# 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/.
+<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/"?>
 <?xml-stylesheet href="chrome://browser/skin/sanitizeDialog.css"?>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
-#endif
 
 <?xml-stylesheet href="chrome://browser/content/sanitizeDialog.css"?>
 
 <!DOCTYPE prefwindow [
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
   %brandDTD;
   %sanitizeDTD;
@@ -31,26 +28,16 @@
 
   <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
     <stringbundle id="bundleBrowser"
                   src="chrome://browser/locale/browser.properties"/>
 
     <script type="application/javascript"
             src="chrome://browser/content/sanitize.js"/>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-    <script type="application/javascript"
-            src="chrome://global/content/globalOverlay.js"/>
-    <script type="application/javascript"
-            src="chrome://browser/content/places/treeView.js"/>
-    <script type="application/javascript"><![CDATA[
-      Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
-      Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
-    ]]></script>
-#endif
 
     <script type="application/javascript"
             src="chrome://browser/content/sanitizeDialog.js"/>
 
     <preferences id="sanitizePreferences">
       <preference id="privacy.cpd.history"               name="privacy.cpd.history"               type="bool"/>
       <preference id="privacy.cpd.formdata"              name="privacy.cpd.formdata"              type="bool"/>
       <preference id="privacy.cpd.downloads"             name="privacy.cpd.downloads"             type="bool" disabled="true"/>
@@ -72,65 +59,43 @@
              accesskey="&clearTimeDuration.accesskey;"
              control="sanitizeDurationChoice"
              id="sanitizeDurationLabel"/>
       <menulist id="sanitizeDurationChoice"
                 preference="privacy.sanitize.timeSpan"
                 onselect="gSanitizePromptDialog.selectByTimespan();"
                 flex="1">
         <menupopup id="sanitizeDurationPopup">
-#ifdef CRH_DIALOG_TREE_VIEW
-          <menuitem label="" value="-1" id="sanitizeDurationCustom"/>
-#endif
           <menuitem label="&clearTimeDuration.lastHour;" value="1"/>
           <menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
           <menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
           <menuitem label="&clearTimeDuration.today;" value="4"/>
           <menuseparator/>
           <menuitem label="&clearTimeDuration.everything;" value="0"/>
         </menupopup>
       </menulist>
       <label id="sanitizeDurationSuffixLabel"
              value="&clearTimeDuration.suffix;"/>
     </hbox>
 
     <separator class="thin"/>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-    <deck id="durationDeck">
-      <tree id="placesTree" flex="1" hidecolumnpicker="true" rows="10"
-            disabled="true" disableKeyNavigation="true">
-        <treecols>
-          <treecol id="date" label="&clearTimeDuration.dateColumn;" flex="1"/>
-          <splitter class="tree-splitter"/>
-          <treecol id="title" label="&clearTimeDuration.nameColumn;" flex="5"/>
-        </treecols>
-        <treechildren id="placesTreechildren"
-                      ondragstart="gSanitizePromptDialog.grippyMoved('ondragstart', event);"
-                      ondragover="gSanitizePromptDialog.grippyMoved('ondragover', event);"
-                      onkeypress="gSanitizePromptDialog.grippyMoved('onkeypress', event);"
-                      onmousedown="gSanitizePromptDialog.grippyMoved('onmousedown', event);"/>
-      </tree>
-#endif
 
       <vbox id="sanitizeEverythingWarningBox">
         <spacer flex="1"/>
         <hbox align="center">
           <image id="sanitizeEverythingWarningIcon"/>
           <vbox id="sanitizeEverythingWarningDescBox" flex="1">
             <description id="sanitizeEverythingWarning"/>
             <description id="sanitizeEverythingUndoWarning">&sanitizeEverythingUndoWarning;</description>
           </vbox>
         </hbox>
         <spacer flex="1"/>
       </vbox>
 
-#ifdef CRH_DIALOG_TREE_VIEW
-    </deck>
-#endif
 
     <separator class="thin"/>
 
     <hbox id="detailsExpanderWrapper" align="center">
       <button type="image"
               id="detailsExpander"
               class="expander-down"
               persist="class"
--- a/browser/base/content/sanitizeDialog.js
+++ b/browser/base/content/sanitizeDialog.js
@@ -251,639 +251,12 @@ var gSanitizePromptDialog = {
   toggleItemList: function ()
   {
     var itemList = document.getElementById("itemList");
 
     if (itemList.collapsed)
       this.showItemList();
     else
       this.hideItemList();
-  },
-
-#ifdef CRH_DIALOG_TREE_VIEW
-  // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
-  // Sanitizer.TIMESPAN_2HOURS, et al.  This should match the value attribute
-  // of the sanitizeDurationCustom menuitem.
-  get TIMESPAN_CUSTOM()
-  {
-    return -1;
-  },
-
-  get placesTree()
-  {
-    if (!this._placesTree)
-      this._placesTree = document.getElementById("placesTree");
-    return this._placesTree;
-  },
-
-  init: function ()
-  {
-    // This is used by selectByTimespan() to determine if the window has loaded.
-    this._inited = true;
-
-    var s = new Sanitizer();
-    s.prefDomain = "privacy.cpd.";
-
-    document.documentElement.getButton("accept").label =
-      this.bundleBrowser.getString("sanitizeButtonOK");
-
-    this.selectByTimespan();
-  },
-
-  /**
-   * Sets up the hashes this.durationValsToRows, which maps duration values
-   * to rows in the tree, this.durationRowsToVals, which maps rows in
-   * the tree to duration values, and this.durationStartTimes, which maps
-   * duration values to their corresponding start times.
-   */
-  initDurationDropdown: function ()
-  {
-    // First, calculate the start times for each duration.
-    this.durationStartTimes = {};
-    var durVals = [];
-    var durPopup = document.getElementById("sanitizeDurationPopup");
-    var durMenuitems = durPopup.childNodes;
-    for (let i = 0; i < durMenuitems.length; i++) {
-      let durMenuitem = durMenuitems[i];
-      let durVal = parseInt(durMenuitem.value);
-      if (durMenuitem.localName === "menuitem" &&
-          durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
-          durVal !== this.TIMESPAN_CUSTOM) {
-        durVals.push(durVal);
-        let durTimes = Sanitizer.getClearRange(durVal);
-        this.durationStartTimes[durVal] = durTimes[0];
-      }
-    }
-
-    // Sort the duration values ascending.  Because one tree index can map to
-    // more than one duration, this ensures that this.durationRowsToVals maps
-    // a row index to the largest duration possible in the code below.
-    durVals.sort();
-
-    // Now calculate the rows in the tree of the durations' start times.  For
-    // each duration, we are looking for the node in the tree whose time is the
-    // smallest time greater than or equal to the duration's start time.
-    this.durationRowsToVals = {};
-    this.durationValsToRows = {};
-    var view = this.placesTree.view;
-    // For all rows in the tree except the grippy row...
-    for (let i = 0; i < view.rowCount - 1; i++) {
-      let unfoundDurVals = [];
-      let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
-                     nodeForTreeIndex(i).time;
-      // For all durations whose rows have not yet been found in the tree, see
-      // if index i is their index.  An index may map to more than one duration,
-      // in which case the final duration (the largest) wins.
-      for (let j = 0; j < durVals.length; j++) {
-        let durVal = durVals[j];
-        let durStartTime = this.durationStartTimes[durVal];
-        if (nodeTime < durStartTime) {
-          this.durationValsToRows[durVal] = i - 1;
-          this.durationRowsToVals[i - 1] = durVal;
-        }
-        else
-          unfoundDurVals.push(durVal);
-      }
-      durVals = unfoundDurVals;
-    }
-
-    // If any durations were not found above, then every node in the tree has a
-    // time greater than or equal to the duration.  In other words, those
-    // durations include the entire tree (except the grippy row).
-    for (let i = 0; i < durVals.length; i++) {
-      let durVal = durVals[i];
-      this.durationValsToRows[durVal] = view.rowCount - 2;
-      this.durationRowsToVals[view.rowCount - 2] = durVal;
-    }
-  },
-
-  /**
-   * If the Places tree is not set up, sets it up.  Otherwise does nothing.
-   */
-  ensurePlacesTreeIsInited: function ()
-  {
-    if (this._placesTreeIsInited)
-      return;
-
-    this._placesTreeIsInited = true;
-
-    // Either "Last Four Hours" or "Today" will have the most history.  If
-    // it's been more than 4 hours since today began, "Today" will. Otherwise
-    // "Last Four Hours" will.
-    var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
-
-    // If it's been less than 4 hours since today began, use the past 4 hours.
-    if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
-      times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
-    }
-
-    var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
-                   getService(Ci.nsINavHistoryService);
-    var query = histServ.getNewQuery();
-    query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
-    query.beginTime = times[0];
-    query.endTimeReference = query.TIME_RELATIVE_EPOCH;
-    query.endTime = times[1];
-    var opts = histServ.getNewQueryOptions();
-    opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
-    opts.queryType = opts.QUERY_TYPE_HISTORY;
-    var result = histServ.executeQuery(query, opts);
-
-    var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
-                                                      new PlacesTreeView());
-    result.addObserver(view, false);
-    this.initDurationDropdown();
-  },
-
-  /**
-   * Called on select of the duration dropdown and when grippyMoved() sets a
-   * duration based on the location of the grippy row.  Selects all the nodes in
-   * the tree that are contained in the selected duration.  If clearing
-   * everything, the warning panel is shown instead.
-   */
-  selectByTimespan: function ()
-  {
-    // This method is the onselect handler for the duration dropdown.  As a
-    // result it's called a couple of times before onload calls init().
-    if (!this._inited)
-      return;
-
-    var durDeck = document.getElementById("durationDeck");
-    var durList = document.getElementById("sanitizeDurationChoice");
-    var durVal = parseInt(durList.value);
-    var durCustom = document.getElementById("sanitizeDurationCustom");
-
-    // If grippy row is not at a duration boundary, show the custom menuitem;
-    // otherwise, hide it.  Since the user cannot specify a custom duration by
-    // using the dropdown, this conditional is true only when this method is
-    // called onselect from grippyMoved(), so no selection need be made.
-    if (durVal === this.TIMESPAN_CUSTOM) {
-      durCustom.hidden = false;
-      return;
-    }
-    durCustom.hidden = true;
-
-    // If clearing everything, show the warning and change the dialog's title.
-    if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
-      this.prepareWarning();
-      durDeck.selectedIndex = 1;
-      window.document.title =
-        this.bundleBrowser.getString("sanitizeDialog2.everything.title");
-      document.documentElement.getButton("accept").disabled = false;
-      return;
-    }
-
-    // Otherwise -- if clearing a specific time range -- select that time range
-    // in the tree.
-    this.ensurePlacesTreeIsInited();
-    durDeck.selectedIndex = 0;
-    window.document.title =
-      window.document.documentElement.getAttribute("noneverythingtitle");
-    var durRow = this.durationValsToRows[durVal];
-    gContiguousSelectionTreeHelper.rangedSelect(durRow);
-    gContiguousSelectionTreeHelper.scrollToGrippy();
-
-    // If duration is empty (there are no selected rows), disable the dialog's
-    // OK button.
-    document.documentElement.getButton("accept").disabled = durRow < 0;
-  },
-
-  sanitize: function ()
-  {
-    // Update pref values before handing off to the sanitizer (bug 453440)
-    this.updatePrefs();
-    var s = new Sanitizer();
-    s.prefDomain = "privacy.cpd.";
-
-    var durList = document.getElementById("sanitizeDurationChoice");
-    var durValue = parseInt(durList.value);
-    s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
-
-    // Set the sanitizer's time range if we're not clearing everything.
-    if (!s.ignoreTimespan) {
-      // If user selected a custom timespan, use that.
-      if (durValue === this.TIMESPAN_CUSTOM) {
-        var view = this.placesTree.view;
-        var now = Date.now() * 1000;
-        // We disable the dialog's OK button if there's no selection, but we'll
-        // handle that case just in... case.
-        if (view.selection.getRangeCount() === 0)
-          s.range = [now, now];
-        else {
-          var startIndexRef = {};
-          // Tree sorted by visit date DEscending, so start time time comes last.
-          view.selection.getRangeAt(0, {}, startIndexRef);
-          view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
-          var startNode = view.nodeForTreeIndex(startIndexRef.value);
-          s.range = [startNode.time, now];
-        }
-      }
-      // Otherwise use the predetermined range.
-      else
-        s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
-    }
-
-    try {
-      s.sanitize(); // We ignore the resulting Promise
-    } catch (er) {
-      Components.utils.reportError("Exception during sanitize: " + er);
-    }
-    return true;
-  },
-
-  /**
-   * In order to mark the custom Places tree view and its nsINavHistoryResult
-   * for garbage collection, we need to break the reference cycle between the
-   * two.
-   */
-  unload: function ()
-  {
-    let result = this.placesTree.getResult();
-    result.removeObserver(this.placesTree.view);
-    this.placesTree.view = null;
-  },
-
-  /**
-   * Called when the user moves the grippy by dragging it, clicking in the tree,
-   * or on keypress.  Updates the duration dropdown so that it displays the
-   * appropriate specific or custom duration.
-   *
-   * @param aEventName
-   *        The name of the event whose handler called this method, e.g.,
-   *        "ondragstart", "onkeypress", etc.
-   * @param aEvent
-   *        The event captured in the event handler.
-   */
-  grippyMoved: function (aEventName, aEvent)
-  {
-    gContiguousSelectionTreeHelper[aEventName](aEvent);
-    var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
-    var durList = document.getElementById("sanitizeDurationChoice");
-    var durValue = parseInt(durList.value);
-
-    // Multiple durations can map to the same row.  Don't update the dropdown
-    // if the current duration is valid for lastSelRow.
-    if ((durValue !== this.TIMESPAN_CUSTOM ||
-         lastSelRow in this.durationRowsToVals) &&
-        (durValue === this.TIMESPAN_CUSTOM ||
-         this.durationValsToRows[durValue] !== lastSelRow)) {
-      // Setting durList.value causes its onselect handler to fire, which calls
-      // selectByTimespan().
-      if (lastSelRow in this.durationRowsToVals)
-        durList.value = this.durationRowsToVals[lastSelRow];
-      else
-        durList.value = this.TIMESPAN_CUSTOM;
-    }
-
-    // If there are no selected rows, disable the dialog's OK button.
-    document.documentElement.getButton("accept").disabled = lastSelRow < 0;
   }
-#endif
-
-};
 
 
-#ifdef CRH_DIALOG_TREE_VIEW
-/**
- * A helper for handling contiguous selection in the tree.
- */
-var gContiguousSelectionTreeHelper = {
-
-  /**
-   * Gets the tree associated with this helper.
-   */
-  get tree()
-  {
-    return this._tree;
-  },
-
-  /**
-   * Sets the tree that this module handles.  The tree is assigned a new view
-   * that is equipped to handle contiguous selection.  You can pass in an
-   * object that will be used as the prototype of the new view.  Otherwise
-   * the tree's current view is used as the prototype.
-   *
-   * @param  aTreeElement
-   *         The tree element
-   * @param  aProtoTreeView
-   *         If defined, this will be used as the prototype of the tree's new
-   *         view
-   * @return The new view
-   */
-  setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
-  {
-    this._tree = aTreeElement;
-    var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
-    aTreeElement.view = newView;
-    return newView;
-  },
-
-  /**
-   * The index of the row that the grippy occupies.  Note that the index of the
-   * last selected row is getGrippyRow() - 1.  If getGrippyRow() is 0, then
-   * no selection exists.
-   *
-   * @return The row index of the grippy
-   */
-  getGrippyRow: function CSTH_getGrippyRow()
-  {
-    var sel = this.tree.view.selection;
-    var rangeCount = sel.getRangeCount();
-    if (rangeCount === 0)
-      return 0;
-    if (rangeCount !== 1) {
-      throw "contiguous selection tree helper: getGrippyRow called with " +
-            "multiple selection ranges";
-    }
-    var max = {};
-    sel.getRangeAt(0, {}, max);
-    return max.value + 1;
-  },
-
-  /**
-   * Helper function for the dragover event.  Your dragover listener should
-   * call this.  It updates the selection in the tree under the mouse.
-   *
-   * @param aEvent
-   *        The observed dragover event
-   */
-  ondragover: function CSTH_ondragover(aEvent)
-  {
-    // Without this when dragging on Windows the mouse cursor is a "no" sign.
-    // This makes it a drop symbol.
-    var ds = Cc["@mozilla.org/widget/dragservice;1"].
-             getService(Ci.nsIDragService).
-             getCurrentSession();
-    ds.canDrop = true;
-    ds.dragAction = 0;
-
-    var tbo = this.tree.treeBoxObject;
-    aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
-    var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
-
-    if (hoverRow < 0)
-      return;
-
-    this.rangedSelect(hoverRow - 1);
-  },
-
-  /**
-   * Helper function for the dragstart event.  Your dragstart listener should
-   * call this.  It starts a drag session.
-   *
-   * @param aEvent
-   *        The observed dragstart event
-   */
-  ondragstart: function CSTH_ondragstart(aEvent)
-  {
-    var tbo = this.tree.treeBoxObject;
-    var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
-
-    if (clickedRow !== this.getGrippyRow())
-      return;
-
-    // This part is a hack.  What we really want is a grab and slide, not
-    // drag and drop.  Start a move drag session with dummy data and a
-    // dummy region.  Set the region's coordinates to (Infinity, Infinity)
-    // so it's drawn offscreen and its size to (1, 1).
-    var arr = Cc["@mozilla.org/array;1"].
-              createInstance(Ci.nsIMutableArray);
-    var trans = Cc["@mozilla.org/widget/transferable;1"].
-                createInstance(Ci.nsITransferable);
-    trans.init(null);
-    trans.setTransferData('dummy-flavor', null, 0);
-    arr.appendElement(trans, /* weak = */ false);
-    var reg = Cc["@mozilla.org/gfx/region;1"].
-              createInstance(Ci.nsIScriptableRegion);
-    reg.setToRect(Infinity, Infinity, 1, 1);
-    var ds = Cc["@mozilla.org/widget/dragservice;1"].
-             getService(Ci.nsIDragService);
-    ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
-  },
-
-  /**
-   * Helper function for the keypress event.  Your keypress listener should
-   * call this.  Users can use Up, Down, Page Up/Down, Home, and End to move
-   * the bottom of the selection window.
-   *
-   * @param aEvent
-   *        The observed keypress event
-   */
-  onkeypress: function CSTH_onkeypress(aEvent)
-  {
-    var grippyRow = this.getGrippyRow();
-    var tbo = this.tree.treeBoxObject;
-    var rangeEnd;
-    switch (aEvent.keyCode) {
-    case aEvent.DOM_VK_HOME:
-      rangeEnd = 0;
-      break;
-    case aEvent.DOM_VK_PAGE_UP:
-      rangeEnd = grippyRow - tbo.getPageLength();
-      break;
-    case aEvent.DOM_VK_UP:
-      rangeEnd = grippyRow - 2;
-      break;
-    case aEvent.DOM_VK_DOWN:
-      rangeEnd = grippyRow;
-      break;
-    case aEvent.DOM_VK_PAGE_DOWN:
-      rangeEnd = grippyRow + tbo.getPageLength();
-      break;
-    case aEvent.DOM_VK_END:
-      rangeEnd = this.tree.view.rowCount - 2;
-      break;
-    default:
-      return;
-      break;
-    }
-
-    aEvent.stopPropagation();
-
-    // First, clip rangeEnd.  this.rangedSelect() doesn't clip the range if we
-    // select past the ends of the tree.
-    if (rangeEnd < 0)
-      rangeEnd = -1;
-    else if (this.tree.view.rowCount - 2 < rangeEnd)
-      rangeEnd = this.tree.view.rowCount - 2;
-
-    // Next, (de)select.
-    this.rangedSelect(rangeEnd);
-
-    // Finally, scroll the tree.  We always want one row above and below the
-    // grippy row to be visible if possible.
-    if (rangeEnd < grippyRow) // moved up
-      tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
-    else {                    // moved down
-      if (rangeEnd + 2 < this.tree.view.rowCount)
-        tbo.ensureRowIsVisible(rangeEnd + 2);
-      else if (rangeEnd + 1 < this.tree.view.rowCount)
-        tbo.ensureRowIsVisible(rangeEnd + 1);
-    }
-  },
-
-  /**
-   * Helper function for the mousedown event.  Your mousedown listener should
-   * call this.  Users can click on individual rows to make the selection
-   * jump to them immediately.
-   *
-   * @param aEvent
-   *        The observed mousedown event
-   */
-  onmousedown: function CSTH_onmousedown(aEvent)
-  {
-    var tbo = this.tree.treeBoxObject;
-    var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
-
-    if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
-      return;
-
-    if (clickedRow < this.getGrippyRow())
-      this.rangedSelect(clickedRow);
-    else if (clickedRow > this.getGrippyRow())
-      this.rangedSelect(clickedRow - 1);
-  },
-
-  /**
-   * Selects range [0, aEndRow] in the tree.  The grippy row will then be at
-   * index aEndRow + 1.  aEndRow may be -1, in which case the selection is
-   * cleared and the grippy row will be at index 0.
-   *
-   * @param aEndRow
-   *        The range [0, aEndRow] will be selected.
-   */
-  rangedSelect: function CSTH_rangedSelect(aEndRow)
-  {
-    var tbo = this.tree.treeBoxObject;
-    if (aEndRow < 0)
-      this.tree.view.selection.clearSelection();
-    else
-      this.tree.view.selection.rangedSelect(0, aEndRow, false);
-    tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
-  },
-
-  /**
-   * Scrolls the tree so that the grippy row is in the center of the view.
-   */
-  scrollToGrippy: function CSTH_scrollToGrippy()
-  {
-    var rowCount = this.tree.view.rowCount;
-    var tbo = this.tree.treeBoxObject;
-    var pageLen = tbo.getPageLength() ||
-                  parseInt(this.tree.getAttribute("rows")) ||
-                  10;
-
-    // All rows fit on a single page.
-    if (rowCount <= pageLen)
-      return;
-
-    var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
-
-    // Grippy row is in first half of first page.
-    if (scrollToRow < 0)
-      scrollToRow = 0;
-
-    // Grippy row is in last half of last page.
-    else if (rowCount < scrollToRow + pageLen)
-      scrollToRow = rowCount - pageLen;
-
-    tbo.scrollToRow(scrollToRow);
-  },
-
-  /**
-   * Creates a new tree view suitable for contiguous selection.  If
-   * aProtoTreeView is specified, it's used as the new view's prototype.
-   * Otherwise the tree's current view is used as the prototype.
-   *
-   * @param aProtoTreeView
-   *        Used as the new view's prototype if specified
-   */
-  _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
-  {
-    var view = aProtoTreeView;
-    var that = this;
-
-    //XXXadw: When Alex gets the grippy icon done, this may or may not change,
-    //        depending on how we style it.
-    view.isSeparator = function CSTH_View_isSeparator(aRow)
-    {
-      return aRow === that.getGrippyRow();
-    };
-
-    // rowCount includes the grippy row.
-    view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
-    view.__defineGetter__("rowCount",
-      function CSTH_View_rowCount()
-      {
-        return this._rowCount + 1;
-      });
-
-    // This has to do with visual feedback in the view itself, e.g., drawing
-    // a small line underneath the dropzone.  Not what we want.
-    view.canDrop = function CSTH_View_canDrop() { return false; };
-
-    // No clicking headers to sort the tree or sort feedback on columns.
-    view.cycleHeader = function CSTH_View_cycleHeader() {};
-    view.sortingChanged = function CSTH_View_sortingChanged() {};
-
-    // Override a bunch of methods to account for the grippy row.
-
-    view._getCellProperties = view.getCellProperties;
-    view.getCellProperties =
-      function CSTH_View_getCellProperties(aRow, aCol)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "grippyRow";
-        if (aRow < grippyRow)
-          return this._getCellProperties(aRow, aCol);
-
-        return this._getCellProperties(aRow - 1, aCol);
-      };
-
-    view._getRowProperties = view.getRowProperties;
-    view.getRowProperties =
-      function CSTH_View_getRowProperties(aRow)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "grippyRow";
-
-        if (aRow < grippyRow)
-          return this._getRowProperties(aRow);
-
-        return this._getRowProperties(aRow - 1);
-      };
-
-    view._getCellText = view.getCellText;
-    view.getCellText =
-      function CSTH_View_getCellText(aRow, aCol)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "";
-        aRow = aRow < grippyRow ? aRow : aRow - 1;
-        return this._getCellText(aRow, aCol);
-      };
-
-    view._getImageSrc = view.getImageSrc;
-    view.getImageSrc =
-      function CSTH_View_getImageSrc(aRow, aCol)
-      {
-        var grippyRow = that.getGrippyRow();
-        if (aRow === grippyRow)
-          return "";
-        aRow = aRow < grippyRow ? aRow : aRow - 1;
-        return this._getImageSrc(aRow, aCol);
-      };
-
-    view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
-    view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
-    view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
-    view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
-    {
-      return aRow < this.rowCount - 1;
-    };
-
-    return view;
-  }
 };
-#endif
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3828,18 +3828,26 @@
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
               window.addEventListener("sizemodechange", this);
               window.addEventListener("SwapDocShells", this, true);
               window.addEventListener("EndSwapDocShells", this, true);
               window.addEventListener("MozTabChildNotReady", this, true);
+
+              let tab = this.requestedTab;
+              let browser = tab.linkedBrowser;
+              let tabIsLoaded = !browser.isRemoteBrowser ||
+                                browser.frameLoader.tabParent.hasPresented;
+
               if (!this.minimized) {
-                this.setTabState(this.requestedTab, this.STATE_LOADED);
+                this.log("Initial tab is loaded?: " + tabIsLoaded);
+                this.setTabState(tab, tabIsLoaded ? this.STATE_LOADED
+                                                  : this.STATE_LOADING);
               }
             },
 
             destroy() {
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
                 this.unloadTimer = null;
               }
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -1,8 +1,15 @@
+###############################################################################
+# DO NOT ADD MORE TESTS HERE.                                                 #
+# TRY ONE OF THE MORE TOPICAL SIBLING DIRECTORIES.                            #
+# THIS DIRECTORY HAS 200+ TESTS AND TAKES AGES TO RUN ON A DEBUG BUILD.       #
+# PLEASE, FOR THE LOVE OF WHATEVER YOU HOLD DEAR, DO NOT ADD MORE TESTS HERE. #
+###############################################################################
+
 [DEFAULT]
 support-files =
   POSTSearchEngine.xml
   accounts_testRemoteCommands.html
   alltabslistener.html
   app_bug575561.html
   app_subframe_bug575561.html
   aboutHome_content_script.js
@@ -81,350 +88,587 @@ support-files =
   !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html
   !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
   !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
 
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutAccounts.js]
 skip-if = os == "linux" # Bug 958026
 support-files =
   content_aboutAccounts.js
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutCertError.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutNetError.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutSupport.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutSupport_newtab_security_state.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutHealthReport.js]
 skip-if = os == "linux" # Bug 924307
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutHome.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_aboutHome_wrapsCorrectly.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_addKeywordSearch.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_alltabslistener.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_audioTabIcon.js]
 tags = audiochannel
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_backButtonFitts.js]
 skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_beforeunload_duplicate_dialogs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_blob-channelname.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bookmark_popup.js]
 skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bookmark_titles.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug321000.js]
 subsuite = clipboard
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug356571.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug380960.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug386835.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug406216.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug408415.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug409481.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug409624.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug413915.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug416661.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug417483.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug419612.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug422590.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug423833.js]
 skip-if = true # bug 428712
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug424101.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug427559.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug431826.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug432599.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug435325.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug441778.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug455852.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug462289.js]
 skip-if = toolkit == "cocoa"
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug462673.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug477014.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug479408.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug481560.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug484315.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug491431.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug495058.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug519216.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug520538.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug521216.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug533232.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug537013.js]
 subsuite = clipboard
 skip-if = e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug537474.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug550565.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug553455.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug555224.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug555767.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug559991.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug561636.js]
 skip-if = true # bug 1057615
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug563588.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug565575.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug567306.js]
 subsuite = clipboard
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug1261299.js]
 subsuite = clipboard
 skip-if = toolkit != "cocoa" # Because of tests for supporting Service Menu of macOS, bug 1261299
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug1297539.js]
 skip-if = toolkit != "cocoa" # Because of tests for supporting pasting from Service Menu of macOS, bug 1297539
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug575561.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug575830.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug577121.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug578534.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug579872.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug580638.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug580956.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug581242.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug581253.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug585558.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug585785.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug585830.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug590206.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug592338.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug594131.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug595507.js]
 skip-if = true # bug 1057615
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug596687.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug597218.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug609700.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug623893.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug624734.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug633691.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug647886.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug655584.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug664672.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug676619.js]
 skip-if = os == "mac" # mac: Intermittent failures, bug 925225
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug678392.js]
 skip-if = os == "mac" # Bug 1102331 - does focus things on the content window which break in e10s mode (still causes orange on Mac 10.10)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug710878.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug719271.js]
 skip-if = os == "win" && debug && e10s # Bug 1315042
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug724239.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug734076.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug735471.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug749738.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug763468_perwindowpb.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug767836_perwindowpb.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug817947.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug832435.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug839103.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug882977.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug970746.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug1015721.js]
 skip-if = os == 'win'
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_accesskeys.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_clipboard.js]
 subsuite = clipboard
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_clipboard_pastefile.js]
 skip-if = true # Disabled due to the clipboard not supporting real file types yet (bug 1288773)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_compacttheme.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contentAreaClick.js]
 skip-if = e10s # Clicks in content don't go through contentAreaClick with e10s.
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contentAltClick.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contextmenu.js]
 subsuite = clipboard
 tags = fullscreen
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contextmenu_input.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_ctrlTab.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_datachoices_notification.js]
 skip-if = !datareporting
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_decoderDoctor.js]
 skip-if = os == "mac" # decoder doctor isn't implemented on osx
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_discovery.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_double_close_tab.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_documentnavigation.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_duplicateIDs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_drag.js]
 skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_favicon_change.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_favicon_change_not_in_document.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_findbarClose.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_focusonkeydown.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_fullscreen-window-open.js]
 tags = fullscreen
 skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_fxaccounts.js]
 support-files = fxa_profile_handler.sjs
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_fxa_web_channel.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_gestureSupport.js]
 skip-if = e10s # Bug 863514 - no gesture support.
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_getshortcutoruri.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_hide_removing.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_homeDrop.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_invalid_uri_back_forward_manipulation.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_keywordBookmarklets.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_keywordSearch.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_keywordSearch_postData.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_middleMouse_noJSPaste.js]
 subsuite = clipboard
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_minimize.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_modifiedclick_inherit_principal.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_new_http_window_opened_from_file_tab.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_offlineQuotaNotification.js]
 skip-if = os == "linux" && !debug # bug 1304273
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_feed_discovery.js]
 support-files = feed_discovery.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_gZipOfflineChild.js]
 support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_overflowScroll.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_page_style_menu.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_page_style_menu_update.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_permissions.js]
 support-files =
   permissions.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_pinnedTabs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_plainTextLinks.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_popupUI.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_popup_blocker.js]
 skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_printpreview.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_private_browsing_window.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_private_no_prompt.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_purgehistory_clears_sh.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_PageMetaData_pushstate.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_refreshBlocker.js]
 support-files =
   refresh_header.sjs
   refresh_meta.sjs
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_relatedTabs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_remoteTroubleshoot.js]
 skip-if = !updater
 reason = depends on UpdateUtils .Locale
 support-files =
   test_remoteTroubleshoot.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_remoteWebNavigation_postdata.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_removeTabsToTheEnd.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_restore_isAppTab.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_sanitize-passwordDisabledHosts.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_sanitize-sitepermissions.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_sanitize-timespans.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_sanitizeDialog.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_link-perwindowpb.js]
 skip-if = e10s && debug && os == "win" # Bug 1280505
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_private_link_perwindowpb.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_link_when_window_navigates.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_video.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_video_frame.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_scope.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contentSearchUI.js]
 support-files =
   contentSearchUI.html
   contentSearchUI.js
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_selectTabAtIndex.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_ssl_error_reports.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_star_hsts.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_storagePressure_notification.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_subframe_favicons_not_used.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_syncui.js]
 skip-if = os == 'linux' # Bug 1304272
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tab_close_dependent_window.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabDrop.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabReorder.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tab_detach_restore.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tab_drag_drop_perwindow.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tab_dragdrop.js]
 skip-if = buildapp == 'mulet' || (e10s && (debug || os == 'linux')) # Bug 1312436
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tab_dragdrop2.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabbar_big_widgets.js]
 skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
                                        # Disabled on OS X because of bug 967917
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabfocus.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabkeynavigation.js]
 skip-if = (os == "mac" && !e10s) # Bug 1237713 - OSX eats keypresses for some reason
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabopen_reflows.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_close_beforeunload.js]
 support-files =
   close_beforeunload_opens_second_tab.html
   close_beforeunload.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_isActive.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_owner.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_temporary_permissions.js]
 support-files =
   permissions.html
   temporary_permissions_subframe.html
   ../webrtc/get_user_media.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_temporary_permissions_navigation.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_temporary_permissions_tabs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
 run-if = e10s
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_1.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
   benignPage.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_2.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
   benignPage.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_3.js]
 tags = trackingprotection
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_4.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
   benignPage.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_5.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_6.js]
 tags = trackingprotection
 support-files =
   file_trackingUI_6.html
   file_trackingUI_6.js
   file_trackingUI_6.js^headers^
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_trackingUI_telemetry.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_typeAheadFind.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_unknownContentType_title.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_unloaddialogs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_utilityOverlay.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_viewSourceInTabOnViewSource.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleFindSelection.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs_bookmarkAllPages.js]
 skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs_bookmarkAllTabs.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs_contextMenu.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug)
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_web_channel.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_windowopen_reflows.js]
 skip-if = os == "mac" # bug 1339317
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_zbug569342.js]
 skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_registerProtocolHandler_notification.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_addCertException.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_about_page_triggeringprincipal.js]
 support-files =
   file_about_child.html
   file_about_parent.html
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_switchbrowser.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_about_process.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_chrome_process.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_javascript.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_blockHPKP.js]
 tags = psm
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_windowactivation.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contextmenu_childprocess.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug963945.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_domFullscreen_fullscreenMode.js]
 tags = fullscreen
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_menuButtonBadgeManager.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_newTabDrop.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_newWindowDrop.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_newwindow_focus.js]
 skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason.
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug1299667.js]
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_close_dependent_tabs.js]
 skip-if = !e10s # GroupedSHistory is e10s-only
+# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -137,18 +137,18 @@ browser.jar:
         content/browser/sync/aboutSyncTabs.xul        (content/sync/aboutSyncTabs.xul)
         content/browser/sync/aboutSyncTabs.js         (content/sync/aboutSyncTabs.js)
         content/browser/sync/aboutSyncTabs.css        (content/sync/aboutSyncTabs.css)
         content/browser/sync/aboutSyncTabs-bindings.xml  (content/sync/aboutSyncTabs-bindings.xml)
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
         content/browser/safeMode.xul                  (content/safeMode.xul)
         content/browser/sanitize.js                   (content/sanitize.js)
-*       content/browser/sanitize.xul                  (content/sanitize.xul)
-*       content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
+        content/browser/sanitize.xul                  (content/sanitize.xul)
+        content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
         content/browser/contentSearchUI.js            (content/contentSearchUI.js)
         content/browser/contentSearchUI.css           (content/contentSearchUI.css)
         content/browser/tabbrowser.css                (content/tabbrowser.css)
         content/browser/tabbrowser.xml                (content/tabbrowser.xml)
         content/browser/urlbarBindings.xml            (content/urlbarBindings.xml)
         content/browser/utilityOverlay.js             (content/utilityOverlay.js)
         content/browser/usercontext.svg               (content/usercontext.svg)
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -658,21 +658,16 @@ const CustomizableWidgets = [
       win.BrowserOpenAddonsMgr();
     }
   }, {
     id: "zoom-controls",
     type: "custom",
     tooltiptext: "zoom-controls.tooltiptext2",
     defaultArea: CustomizableUI.AREA_PANEL,
     onBuild(aDocument) {
-      const kPanelId = "PanelUI-popup";
-      let areaType = CustomizableUI.getAreaType(this.currentArea);
-      let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL;
-      let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR;
-
       let buttons = [{
         id: "zoom-out-button",
         command: "cmd_fullZoomReduce",
         label: true,
         tooltiptext: "tooltiptext2",
         shortcutId: "key_fullZoomReduce",
       }, {
         id: "zoom-reset-button",
@@ -700,135 +695,60 @@ const CustomizableWidgets = [
       buttons.forEach(function(aButton, aIndex) {
         if (aIndex != 0)
           node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
         let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
         setAttributes(btnNode, aButton);
         node.appendChild(btnNode);
       });
 
-      // The middle node is the 'Reset Zoom' button.
-      let zoomResetButton = node.childNodes[2];
-      let window = aDocument.defaultView;
-      function updateZoomResetButton() {
-        let updateDisplay = true;
-        // Label should always show 100% in customize mode, so don't update:
-        if (aDocument.documentElement.hasAttribute("customizing")) {
-          updateDisplay = false;
-        }
-        // XXXgijs in some tests we get called very early, and there's no docShell on the
-        // tabbrowser. This breaks the zoom toolkit code (see bug 897410). Don't let that happen:
-        let zoomFactor = 100;
-        try {
-          zoomFactor = Math.round(window.ZoomManager.zoom * 100);
-        } catch (e) {}
-        zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty(
-          buttons[1], "label", [updateDisplay ? zoomFactor : 100]
-        ));
-      }
-
-      // Register ourselves with the service so we know when the zoom prefs change.
-      Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:location-change", false);
-      window.addEventListener("FullZoomChange", updateZoomResetButton);
-
-      if (inPanel) {
-        let panel = aDocument.getElementById(kPanelId);
-        panel.addEventListener("popupshowing", updateZoomResetButton);
-      } else {
-        if (inToolbar) {
-          let container = window.gBrowser.tabContainer;
-          container.addEventListener("TabSelect", updateZoomResetButton);
-        }
-        updateZoomResetButton();
-      }
       updateCombinedWidgetStyle(node, this.currentArea, true);
 
       let listener = {
         onWidgetAdded: function(aWidgetId, aArea, aPosition) {
           if (aWidgetId != this.id)
             return;
 
           updateCombinedWidgetStyle(node, aArea, true);
-          updateZoomResetButton();
-
-          let newAreaType = CustomizableUI.getAreaType(aArea);
-          if (newAreaType == CustomizableUI.TYPE_MENU_PANEL) {
-            let panel = aDocument.getElementById(kPanelId);
-            panel.addEventListener("popupshowing", updateZoomResetButton);
-          } else if (newAreaType == CustomizableUI.TYPE_TOOLBAR) {
-            let container = window.gBrowser.tabContainer;
-            container.addEventListener("TabSelect", updateZoomResetButton);
-          }
         }.bind(this),
 
         onWidgetRemoved: function(aWidgetId, aPrevArea) {
           if (aWidgetId != this.id)
             return;
 
-          let formerAreaType = CustomizableUI.getAreaType(aPrevArea);
-          if (formerAreaType == CustomizableUI.TYPE_MENU_PANEL) {
-            let panel = aDocument.getElementById(kPanelId);
-            panel.removeEventListener("popupshowing", updateZoomResetButton);
-          } else if (formerAreaType == CustomizableUI.TYPE_TOOLBAR) {
-            let container = window.gBrowser.tabContainer;
-            container.removeEventListener("TabSelect", updateZoomResetButton);
-          }
-
           // When a widget is demoted to the palette ('removed'), it's visual
           // style should change.
           updateCombinedWidgetStyle(node, null, true);
-          updateZoomResetButton();
         }.bind(this),
 
         onWidgetReset: function(aWidgetNode) {
           if (aWidgetNode != node)
             return;
           updateCombinedWidgetStyle(node, this.currentArea, true);
-          updateZoomResetButton();
         }.bind(this),
 
         onWidgetUndoMove: function(aWidgetNode) {
           if (aWidgetNode != node)
             return;
           updateCombinedWidgetStyle(node, this.currentArea, true);
-          updateZoomResetButton();
         }.bind(this),
 
         onWidgetMoved: function(aWidgetId, aArea) {
           if (aWidgetId != this.id)
             return;
           updateCombinedWidgetStyle(node, aArea, true);
-          updateZoomResetButton();
         }.bind(this),
 
         onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
           if (aWidgetId != this.id || aDoc != aDocument)
             return;
 
           CustomizableUI.removeListener(listener);
-          Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:location-change");
-          window.removeEventListener("FullZoomChange", updateZoomResetButton);
-          let panel = aDoc.getElementById(kPanelId);
-          panel.removeEventListener("popupshowing", updateZoomResetButton);
-          let container = aDoc.defaultView.gBrowser.tabContainer;
-          container.removeEventListener("TabSelect", updateZoomResetButton);
         }.bind(this),
 
-        onCustomizeStart(aWindow) {
-          if (aWindow.document == aDocument) {
-            updateZoomResetButton();
-          }
-        },
-
-        onCustomizeEnd(aWindow) {
-          if (aWindow.document == aDocument) {
-            updateZoomResetButton();
-          }
-        },
-
         onWidgetDrag: function(aWidgetId, aArea) {
           if (aWidgetId != this.id)
             return;
           aArea = aArea || this.currentArea;
           updateCombinedWidgetStyle(node, aArea, true);
         }.bind(this)
       };
       CustomizableUI.addListener(listener);
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -60,20 +60,34 @@ function closeGlobalTab() {
   let win = gTab.ownerGlobal;
   if (win.gBrowser.browsers.length == 1) {
     win.BrowserOpenTab();
   }
   win.gBrowser.removeTab(gTab);
   gTab = null;
 }
 
+var gTabsProgressListener = {
+  onLocationChange(aBrowser, aWebProgress, aRequest, aLocation, aFlags) {
+    if (!gTab || gTab.linkedBrowser != aBrowser) {
+      return;
+    }
+
+    unregisterGlobalTab();
+  },
+}
+
 function unregisterGlobalTab() {
   gTab.removeEventListener("TabClose", unregisterGlobalTab);
-  gTab.ownerGlobal.removeEventListener("unload", unregisterGlobalTab);
+  let win = gTab.ownerGlobal;
+  win.removeEventListener("unload", unregisterGlobalTab);
+  win.gBrowser.removeTabsProgressListener(gTabsProgressListener);
+
   gTab.removeAttribute("customizemode");
+
   gTab = null;
 }
 
 function CustomizeMode(aWindow) {
   if (gDisableAnimation === null) {
     gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
                         Services.prefs.getBoolPref(kPrefCustomizationAnimation);
   }
@@ -172,16 +186,19 @@ CustomizeMode.prototype = {
 
     let win = gTab.ownerGlobal;
 
     win.gBrowser.setTabTitle(gTab);
     win.gBrowser.setIcon(gTab,
                          "chrome://browser/skin/customizableui/customizeFavicon.ico");
 
     gTab.addEventListener("TabClose", unregisterGlobalTab);
+
+    win.gBrowser.addTabsProgressListener(gTabsProgressListener);
+
     win.addEventListener("unload", unregisterGlobalTab);
 
     if (gTab.selected) {
       win.gCustomizeMode.enter();
     }
   },
 
   enter() {
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -143,12 +143,13 @@ skip-if = os == "linux" # crashing on Li
 tags = fullscreen
 skip-if = os == "mac"
 [browser_1087303_button_preferences.js]
 [browser_1089591_still_customizable_after_reset.js]
 [browser_1096763_seen_widgets_post_reset.js]
 [browser_1161838_inserted_new_default_buttons.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_customizemode_contextmenu_menubuttonstate.js]
+[browser_exit_background_customize_mode.js]
 [browser_panel_toggle.js]
 [browser_switch_to_customize_mode.js]
 [browser_synced_tabs_menu.js]
 [browser_check_tooltips_in_navbar.js]
--- a/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js
+++ b/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js
@@ -24,19 +24,20 @@ add_task(function*() {
   });
 
   is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
   let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange");
   FullZoom.enlarge();
   yield zoomChangePromise;
   is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
 
-  let tabSelectPromise = promiseTabSelect();
+  let tabSelectPromise = promiseObserverNotification("browser-fullZoom:location-change");
   gBrowser.selectedTab = tab2;
   yield tabSelectPromise;
+  yield new Promise(resolve => executeSoon(resolve));
   is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:robots");
 
   gBrowser.selectedTab = tab1;
   let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset");
   FullZoom.reset();
   yield zoomResetPromise;
   is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
 
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_exit_background_customize_mode.js
@@ -0,0 +1,36 @@
+"use strict";
+
+/**
+ * Tests that if customize mode is currently attached to a background
+ * tab, and that tab browses to a new location, that customize mode
+ * is detached from that tab.
+ */
+add_task(function* test_exit_background_customize_mode() {
+  let nonCustomizingTab = gBrowser.selectedTab;
+
+  Assert.equal(gBrowser.tabContainer.querySelector("tab[customizemode=true]"),
+               null,
+               "Should not have a tab marked as being the customize tab now.");
+
+  yield startCustomizing();
+  is(gBrowser.tabs.length, 2, "Should have 2 tabs");
+
+  let custTab = gBrowser.selectedTab;
+
+  let finishedCustomizing = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization");
+  yield BrowserTestUtils.switchTab(gBrowser, nonCustomizingTab);
+  yield finishedCustomizing;
+
+  custTab.linkedBrowser.loadURI("http://example.com");
+  yield BrowserTestUtils.browserLoaded(custTab.linkedBrowser);
+
+  Assert.equal(gBrowser.tabContainer.querySelector("tab[customizemode=true]"),
+               null,
+               "Should not have a tab marked as being the customize tab now.");
+
+  yield startCustomizing();
+  is(gBrowser.tabs.length, 3, "Should have 3 tabs now");
+
+  yield endCustomizing();
+  yield BrowserTestUtils.removeTab(custTab);
+});
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -45,20 +45,17 @@ function register_module(categoryName, c
     inited: false,
     init() {
       categoryObject.init();
       this.inited = true;
     }
   });
 }
 
-addEventListener("DOMContentLoaded", function onLoad() {
-  removeEventListener("DOMContentLoaded", onLoad);
-  init_all();
-});
+document.addEventListener("DOMContentLoaded", init_all, {once: true});
 
 function init_all() {
   document.documentElement.instantApply = true;
 
   gSubDialog.init();
   register_module("paneGeneral", gMainPane);
   register_module("paneSearch", gSearchPane);
   register_module("panePrivacy", gPrivacyPane);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -127,16 +127,36 @@ const CLOSED_MESSAGES = new Set([
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabBrowserInserted", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
 const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
+/**
+ * When calling restoreTabContent, we can supply a reason why
+ * the content is being restored. These are those reasons.
+ */
+const RESTORE_TAB_CONTENT_REASON = {
+  /**
+   * SET_STATE:
+   * We're restoring this tab's content because we're setting
+   * state inside this browser tab, probably because the user
+   * has asked us to restore a tab (or window, or entire session).
+   */
+  SET_STATE: 0,
+  /**
+   * NAVIGATE_AND_RESTORE:
+   * We're restoring this tab's content because a navigation caused
+   * us to do a remoteness-flip.
+   */
+  NAVIGATE_AND_RESTORE: 1,
+};
+
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/Timer.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
@@ -901,21 +921,25 @@ var SessionStoreInternal = {
         tab.dispatchEvent(event);
         break;
       }
       case "SessionStore:restoreTabContentStarted":
         if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
           // If a load not initiated by sessionstore was started in a
           // previously pending tab. Mark the tab as no longer pending.
           this.markTabAsRestoring(tab);
-        } else if (!data.isRemotenessUpdate) {
+        } else if (data.reason != RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE) {
           // If the user was typing into the URL bar when we crashed, but hadn't hit
           // enter yet, then we just need to write that value to the URL bar without
           // loading anything. This must happen after the load, as the load will clear
           // userTypedValue.
+          //
+          // Note that we only want to do that if we're restoring state for reasons
+          // _other_ than a navigateAndRestore remoteness-flip, as such a flip
+          // implies that the user was navigating.
           let tabData = TabState.collect(tab);
           if (tabData.userTypedValue && !tabData.userTypedClear && !browser.userTypedValue) {
             browser.userTypedValue = tabData.userTypedValue;
             win.URLBarSetURI();
           }
 
           // Remove state we don't need any longer.
           TabStateCache.update(browser, {
@@ -2792,16 +2816,19 @@ var SessionStoreInternal = {
 
       let tabState = TabState.clone(tab);
       let options = {
         restoreImmediately: true,
         // We want to make sure that this information is passed to restoreTab
         // whether or not a historyIndex is passed in. Thus, we extract it from
         // the loadArguments.
         reloadInFreshProcess: !!recentLoadArguments.reloadInFreshProcess,
+        // Make sure that SessionStore knows that this restoration is due
+        // to a navigation, as opposed to us restoring a closed window or tab.
+        restoreContentReason: RESTORE_TAB_CONTENT_REASON.NAVIGATE_AND_RESTORE,
       };
 
       if (historyIndex >= 0) {
         tabState.index = historyIndex + 1;
         tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
       } else {
         options.loadArguments = recentLoadArguments;
       }
@@ -3188,24 +3215,19 @@ var SessionStoreInternal = {
     let numVisibleTabs = 0;
 
     for (var t = 0; t < newTabCount; t++) {
       // When trying to restore into existing tab, we also take the userContextId
       // into account if present.
       let userContextId = winData.tabs[t].userContextId;
       let reuseExisting = t < openTabCount &&
                           (tabbrowser.tabs[t].getAttribute("usercontextid") == (userContextId || ""));
-      // If the tab is pinned, then we'll be loading it right away, and
-      // there's no need to cause a remoteness flip by loading it initially
-      // non-remote.
-      let forceNotRemote = !winData.tabs[t].pinned;
-      let tab = reuseExisting ? tabbrowser.tabs[t] :
-                                tabbrowser.addTab("about:blank",
+      let tab = reuseExisting ? this._maybeUpdateBrowserRemoteness(tabbrowser.tabs[t])
+                              : tabbrowser.addTab("about:blank",
                                                   {skipAnimation: true,
-                                                   forceNotRemote,
                                                    userContextId});
 
       // If we inserted a new tab because the userContextId didn't match with the
       // open tab, even though `t < openTabCount`, we need to remove that open tab
       // and put the newly added tab in its place.
       if (!reuseExisting && t < openTabCount) {
         tabbrowser.removeTab(tabbrowser.tabs[t]);
         tabbrowser.moveTabTo(tab, t);
@@ -3453,28 +3475,26 @@ var SessionStoreInternal = {
 
     let restoreImmediately = options.restoreImmediately;
     let loadArguments = options.loadArguments;
     let browser = tab.linkedBrowser;
     let window = tab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let forceOnDemand = options.forceOnDemand;
     let reloadInFreshProcess = options.reloadInFreshProcess;
+    let restoreContentReason = options.restoreContentReason;
 
     let willRestoreImmediately = restoreImmediately ||
                                  tabbrowser.selectedBrowser == browser ||
                                  loadArguments;
 
     if (!willRestoreImmediately && !forceOnDemand) {
       TabRestoreQueue.add(tab);
     }
 
-    this._maybeUpdateBrowserRemoteness({ tabbrowser, tab,
-                                         willRestoreImmediately });
-
     // Increase the busy state counter before modifying the tab.
     this._setWindowStateBusy(window);
 
     // It's important to set the window state to dirty so that
     // we collect their data for the first time when saving state.
     DirtyWindows.add(window);
 
     // In case we didn't collect/receive data for any tabs yet we'll have to
@@ -3577,17 +3597,18 @@ var SessionStoreInternal = {
     // Restore tab attributes.
     if ("attributes" in tabData) {
       TabAttributes.set(tab, tabData.attributes);
     }
 
     // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
     if (willRestoreImmediately) {
-      this.restoreTabContent(tab, loadArguments, reloadInFreshProcess);
+      this.restoreTabContent(tab, loadArguments, reloadInFreshProcess,
+                             restoreContentReason);
     } else if (!forceOnDemand) {
       this.restoreNextTab();
     }
 
     // Decrease the busy state counter after we're done.
     this._setWindowStateReady(window);
   },
 
@@ -3595,18 +3616,23 @@ var SessionStoreInternal = {
    * Kicks off restoring the given tab.
    *
    * @param aTab
    *        the tab to restore
    * @param aLoadArguments
    *        optional load arguments used for loadURI()
    * @param aReloadInFreshProcess
    *        true if we want to reload into a fresh process
+   * @param aReason
+   *        The reason for why this tab content is being restored.
+   *        Should be one of the values within RESTORE_TAB_CONTENT_REASON.
+   *        Defaults to RESTORE_TAB_CONTENT_REASON.SET_STATE.
    */
-  restoreTabContent(aTab, aLoadArguments = null, aReloadInFreshProcess = false) {
+  restoreTabContent(aTab, aLoadArguments = null, aReloadInFreshProcess = false,
+                    aReason = RESTORE_TAB_CONTENT_REASON.SET_STATE) {
     if (aTab.hasAttribute("customizemode") && !aLoadArguments) {
       return;
     }
 
     let browser = aTab.linkedBrowser;
     let window = aTab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let tabData = TabState.clone(aTab);
@@ -3656,17 +3682,18 @@ var SessionStoreInternal = {
 
     // If the restored browser wants to show view source content, start up a
     // view source browser that will load the required frame script.
     if (uri && ViewSourceBrowser.isViewSource(uri)) {
       new ViewSourceBrowser(browser);
     }
 
     browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
-      {loadArguments: aLoadArguments, isRemotenessUpdate});
+      {loadArguments: aLoadArguments, isRemotenessUpdate,
+       reason: aReason});
   },
 
   /**
    * Marks a given pending tab as restoring.
    *
    * @param aTab
    *        the pending tab to mark as restoring
    */
@@ -3897,56 +3924,31 @@ var SessionStoreInternal = {
       Services.obs.notifyObservers(null, NOTIFY_CLOSED_OBJECTS_CHANGED, null);
     }, 0);
   },
 
   /**
    * Determines whether or not a tab that is being restored needs
    * to have its remoteness flipped first.
    *
-   * @param (object) with the following properties:
-   *
-   *        tabbrowser (<xul:tabbrowser>):
-   *          The tabbrowser that the browser belongs to.
+   * @param tab (<xul:tab>):
+   *        The tab being restored.
    *
-   *        tab (<xul:tab>):
-   *          The tab being restored
-   *
-   *        willRestoreImmediately (bool):
-   *          true if the tab is going to have its content
-   *          restored immediately by the caller.
-   *
+   * @returns tab (<xul:tab>)
+   *        The tab that was passed.
    */
-  _maybeUpdateBrowserRemoteness({ tabbrowser, tab,
-                                  willRestoreImmediately }) {
-    // If the browser we're attempting to restore happens to be
-    // remote, we need to flip it back to non-remote if it's going
-    // to go into the pending background tab state. This is to make
-    // sure that a background tab can't crash if it hasn't yet
-    // been restored.
-    //
-    // Normally, when a window is restored, the tabs that SessionStore
-    // inserts are non-remote - but the initial browser is, by default,
-    // remote, so this check and flip covers this case. The other case
-    // is when window state is overwriting the state of an existing
-    // window with some remote tabs.
+  _maybeUpdateBrowserRemoteness(tab) {
+    let win = tab.ownerGlobal;
+    let tabbrowser = win.gBrowser;
     let browser = tab.linkedBrowser;
-
-    // There are two ways that a tab might start restoring its content
-    // very soon - either the caller is going to restore the content
-    // immediately, or the TabRestoreQueue is set up so that the tab
-    // content is going to be restored in the very near future. In
-    // either case, we don't want to flip remoteness, since the browser
-    // will soon be loading content.
-    let willRestore = willRestoreImmediately ||
-                      TabRestoreQueue.willRestoreSoon(tab);
-
-    if (browser.isRemoteBrowser && !willRestore) {
-      tabbrowser.updateBrowserRemoteness(browser, false);
+    if (win.gMultiProcessBrowser && !browser.isRemoteBrowser) {
+      tabbrowser.updateBrowserRemoteness(browser, true);
     }
+
+    return tab;
   },
 
   /**
    * Update the session start time and send a telemetry measurement
    * for the number of days elapsed since the session was started.
    *
    * @param state
    *        The session state.
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -193,27 +193,29 @@ var MessageListener = {
     // SessionStore.jsm so that it can run SSTabRestoring. Users of
     // SSTabRestoring seem to get confused if chrome and content are out of
     // sync about the state of the restore (particularly regarding
     // docShell.currentURI). Using a synchronous message is the easiest way
     // to temporarily synchronize them.
     sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch, isRemotenessUpdate});
   },
 
-  restoreTabContent({loadArguments, isRemotenessUpdate}) {
+  restoreTabContent({loadArguments, isRemotenessUpdate, reason}) {
     let epoch = gCurrentEpoch;
 
     // We need to pass the value of didStartLoad back to SessionStore.jsm.
     let didStartLoad = gContentRestore.restoreTabContent(loadArguments, isRemotenessUpdate, () => {
       // Tell SessionStore.jsm that it may want to restore some more tabs,
       // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
       sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
     });
 
-    sendAsyncMessage("SessionStore:restoreTabContentStarted", {epoch, isRemotenessUpdate});
+    sendAsyncMessage("SessionStore:restoreTabContentStarted", {
+      epoch, isRemotenessUpdate, reason,
+    });
 
     if (!didStartLoad) {
       // Pretend that the load succeeded so that event handlers fire correctly.
       sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
     }
   },
 
   flush({id}) {
--- a/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
+++ b/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
@@ -99,17 +99,18 @@ const PINNED_STATE = {
  *   restoring state. Each bool in the Array represents the window
  *   tabs in order. A "true" indicates that the tab be remote, and
  *   a "false" indicates that the tab should be "non-remote". We
  *   need this Array in order to test pinned tabs which will also
  *   be loaded by default, and therefore should end up remote.
  *
  */
 function* runScenarios(scenarios) {
-  for (let scenario of scenarios) {
+  for (let [scenarioIndex, scenario] of scenarios.entries()) {
+    info("Running scenario " + scenarioIndex);
     // Let's make sure our scenario is sane first.
     Assert.equal(scenario.expectedFlips.length,
                  scenario.expectedRemoteness.length,
                  "All expected flips and remoteness needs to be supplied");
     Assert.ok(scenario.initialSelectedTab > 0,
               "You must define an initially selected tab");
 
     // First, we need to create the initial conditions, so we
@@ -229,114 +230,114 @@ add_task(function*() {
     // when the restored window is being opened.
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 3,
       // The initial tab is remote and should go into
       // the background state. The second and third tabs
-      // are new and should be initialized non-remote.
-      expectedFlips: [true, false, true],
-      // Only the selected tab should be remote.
-      expectedRemoteness: [false, false, true],
+      // are new and should initialize remotely as well.
+      // There should therefore be no remoteness flips.
+      expectedFlips: [false, false, false],
+      // All tabs should now be remote.
+      expectedRemoteness: [true, true, true],
     },
 
     // A single remote tab, and this is the one that's going
     // to be selected once state is restored.
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 1,
       // The initial tab is remote and selected, so it should
       // not flip remoteness. The other two new tabs should
-      // be non-remote by default.
+      // initialize as remote unrestored background tabs.
       expectedFlips: [false, false, false],
-      // Only the selected tab should be remote.
-      expectedRemoteness: [true, false, false],
+      // All tabs should now be remote.
+      expectedRemoteness: [true, true, true],
     },
 
     // A single remote tab which starts selected. We set the
     // selectedTab to 0 which is equivalent to "don't change
     // the tab selection in the window".
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 0,
       // The initial tab is remote and selected, so it should
       // not flip remoteness. The other two new tabs should
-      // be non-remote by default.
+      // initialize as remote unrestored background tabs.
       expectedFlips: [false, false, false],
-      // Only the selected tab should be remote.
-      expectedRemoteness: [true, false, false],
+      // All tabs should now be remote.
+      expectedRemoteness: [true, true, true],
     },
 
     // An initially remote tab, but we're going to load
     // some pinned tabs now, and the pinned tabs should load
     // right away.
     {
       initialRemoteness: [true],
       initialSelectedTab: 1,
       stateToRestore: PINNED_STATE,
       selectedTab: 3,
       // The initial tab is pinned and will load right away,
       // so it should stay remote. The second tab is new
       // and pinned, so it should start remote and not flip.
       // The third tab is not pinned, but it is selected,
-      // so it will start non-remote, and then flip remoteness.
-      expectedFlips: [false, false, true],
+      // so it will start remote.
+      expectedFlips: [false, false, false],
       // Both pinned tabs and the selected tabs should all
       // end up being remote.
       expectedRemoteness: [true, true, true],
     },
 
     // A single non-remote tab.
     {
       initialRemoteness: [false],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 2,
-      // The initial tab is non-remote and should stay
-      // that way. The second and third tabs are new and
-      // should be initialized non-remote.
-      expectedFlips: [false, true, false],
-      // Only the selected tab should be remote.
-      expectedRemoteness: [false, true, false],
+      // The initial tab is non-remote and should become remote.
+      // The second and third tabs are new and should be initialized
+      // as remote.
+      expectedFlips: [true, false, false],
+      // All tabs should now be remote.
+      expectedRemoteness: [true, true, true],
     },
 
     // A mixture of remote and non-remote tabs.
     {
       initialRemoteness: [true, false, true],
       initialSelectedTab: 1,
       stateToRestore: SIMPLE_STATE,
       selectedTab: 3,
-      // The initial tab is remote and should flip to non-remote
-      // as it is put into the background. The second tab should
-      // stay non-remote, and the third one should stay remote.
-      expectedFlips: [true, false, false],
-      // Only the selected tab should be remote.
-      expectedRemoteness: [false, false, true],
+      // The initial tab is remote and should stay that way, even
+      // when put into the background. The second tab should flip
+      // remoteness, and the third one should stay remote.
+      expectedFlips: [false, true, false],
+      // All tabs should now be remote.
+      expectedRemoteness: [true, true, true],
     },
 
     // An initially non-remote tab, but we're going to load
     // some pinned tabs now, and the pinned tabs should load
     // right away.
     {
       initialRemoteness: [false],
       initialSelectedTab: 1,
       stateToRestore: PINNED_STATE,
       selectedTab: 3,
       // The initial tab is pinned and will load right away,
       // so it should flip remoteness. The second tab is new
       // and pinned, so it should start remote and not flip.
       // The third tab is not pinned, but it is selected,
-      // so it will start non-remote, and then flip remoteness.
-      expectedFlips: [true, false, true],
-      // Both pinned tabs and the selected tabs should all
-      // end up being remote.
+      // so it will start remote, and not flip remoteness.
+      expectedFlips: [true, false, false],
+      // All tabs should now be remote.
       expectedRemoteness: [true, true, true],
     },
   ];
 
   yield* runScenarios(TEST_SCENARIOS);
 });
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -381,20 +381,21 @@ menuUndoCloseWindowSingleTabLabel=#1
 
 # Unified Back-/Forward Popup
 tabHistory.current=Stay on this page
 tabHistory.goBack=Go back to this page
 tabHistory.goForward=Go forward to this page
 
 # URL Bar
 pasteAndGo.label=Paste & Go
-# LOCALIZATION NOTE(urlbar-zoom-button.label): %S is the current zoom level,
+
+# LOCALIZATION NOTE(zoom-button.label): %S is the current page zoom level,
 # %% will be displayed as a single % character (% is commonly used to define
 # format specifiers, so it needs to be escaped).
-urlbar-zoom-button.label = %S%%
+zoom-button.label = %S%%
 
 # Block autorefresh
 refreshBlocked.goButton=Allow
 refreshBlocked.goButton.accesskey=A
 refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
 refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
 
 # General bookmarks button
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -46,20 +46,16 @@ preferences-button.tooltipWin2 = Open op
 
 zoom-controls.label = Zoom Controls
 zoom-controls.tooltiptext2 = Zoom controls
 
 zoom-out-button.label = Zoom out
 # LOCALIZATION NOTE(zoom-out-button.tooltiptext2): %S is the keyboard shortcut.
 zoom-out-button.tooltiptext2 = Zoom out (%S)
 
-# LOCALIZATION NOTE(zoom-reset-button.label): %S is the current zoom level,
-# %% will be displayed as a single % character (% is commonly used to define
-# format specifiers, so it needs to be escaped).
-zoom-reset-button.label = %S%%
 # LOCALIZATION NOTE(zoom-reset-button.tooltiptext2): %S is the keyboard shortcut.
 zoom-reset-button.tooltiptext2 = Reset zoom level (%S)
 
 zoom-in-button.label = Zoom in
 # LOCALIZATION NOTE(zoom-in-button.tooltiptext2): %S is the keyboard shortcut.
 zoom-in-button.tooltiptext2 = Zoom in (%S)
 
 edit-controls.label = Edit Controls
--- a/browser/locales/en-US/chrome/browser/sanitize.dtd
+++ b/browser/locales/en-US/chrome/browser/sanitize.dtd
@@ -31,18 +31,16 @@
 <!ENTITY clearTimeDuration.lastHour       "Last Hour">
 <!ENTITY clearTimeDuration.last2Hours     "Last Two Hours">
 <!ENTITY clearTimeDuration.last4Hours     "Last Four Hours">
 <!ENTITY clearTimeDuration.today          "Today">
 <!ENTITY clearTimeDuration.everything     "Everything">
 <!-- Localization note (clearTimeDuration.suffix) - trailing entity for languages
 that require it.  -->
 <!ENTITY clearTimeDuration.suffix         "">
-<!ENTITY clearTimeDuration.dateColumn     "Visit Date">
-<!ENTITY clearTimeDuration.nameColumn     "Name">
 
 <!-- LOCALIZATION NOTE (detailsProgressiveDisclosure.*): Labels and accesskeys
      of the "Details" progressive disclosure button.  See UI mockup at bug
      480169 -->
 <!ENTITY detailsProgressiveDisclosure.label     "Details">
 <!ENTITY detailsProgressiveDisclosure.accesskey "e">
 
 <!ENTITY historySection.label         "History">
rename from browser/modules/URLBarZoom.jsm
rename to browser/modules/FullZoomUI.jsm
--- a/browser/modules/URLBarZoom.jsm
+++ b/browser/modules/FullZoomUI.jsm
@@ -1,20 +1,20 @@
 // -*- 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/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = [ "URLBarZoom" ];
+this.EXPORTED_SYMBOLS = [ "FullZoomUI" ];
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-var URLBarZoom = {
+var FullZoomUI = {
   init(aWindow) {
     aWindow.addEventListener("EndSwapDocShells", onEndSwapDocShells, true);
     aWindow.addEventListener("FullZoomChange", onFullZoomChange);
     aWindow.addEventListener("unload", () => {
       aWindow.removeEventListener("EndSwapDocShells", onEndSwapDocShells, true);
       aWindow.removeEventListener("FullZoomChange", onFullZoomChange);
     }, {once: true});
   },
@@ -22,70 +22,95 @@ var URLBarZoom = {
 
 function fullZoomLocationChangeObserver(aSubject, aTopic) {
   // If the tab was the last one in its window and has been dragged to another
   // window, the original browser's window will be unavailable here. Since that
   // window is closing, we can just ignore this notification.
   if (!aSubject.ownerGlobal) {
     return;
   }
-
-  updateZoomButton(aSubject, false);
+  updateZoomUI(aSubject, false);
 }
+Services.obs.addObserver(fullZoomLocationChangeObserver, "browser-fullZoom:location-change", false);
 
 function onEndSwapDocShells(event) {
-  updateZoomButton(event.originalTarget);
+  updateZoomUI(event.originalTarget);
 }
 
 function onFullZoomChange(event) {
   let browser;
   if (event.target.nodeType == event.target.DOCUMENT_NODE) {
     // In non-e10s, the event is dispatched on the contentDocument
     // so we need to jump through some hoops to get to the <xul:browser>.
     let gBrowser = event.currentTarget.gBrowser;
     let topDoc = event.target.defaultView.top.document;
     browser = gBrowser.getBrowserForDocument(topDoc);
   } else {
     browser = event.originalTarget;
   }
-  updateZoomButton(browser, true);
+  updateZoomUI(browser, true);
 }
 
-  /**
-   * Updates the zoom button in the location bar.
-   *
-   * @param {object} aBrowser The browser that the zoomed content resides in.
-   * @param {boolean} aAnimate Should be True for all cases unless the zoom
-   *   change is related to tab switching. Optional
-   */
-function updateZoomButton(aBrowser, aAnimate = false) {
+/**
+ * Updates zoom controls.
+ *
+ * @param {object} aBrowser The browser that the zoomed content resides in.
+ * @param {boolean} aAnimate Should be True for all cases unless the zoom
+ *   change is related to tab switching. Optional
+ */
+function updateZoomUI(aBrowser, aAnimate = false) {
   let win = aBrowser.ownerGlobal;
   if (aBrowser != win.gBrowser.selectedBrowser) {
     return;
   }
 
   let customizableZoomControls = win.document.getElementById("zoom-controls");
-  let zoomResetButton = win.document.getElementById("urlbar-zoom-button");
-
-  // Ensure that zoom controls haven't already been added to browser in Customize Mode
-  if (customizableZoomControls &&
-      customizableZoomControls.getAttribute("cui-areatype") == "toolbar") {
-    zoomResetButton.hidden = true;
-    return;
-  }
-
+  let customizableZoomReset = win.document.getElementById("zoom-reset-button");
+  let urlbarZoomButton = win.document.getElementById("urlbar-zoom-button");
   let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
-  if (zoomFactor != 100) {
-    zoomResetButton.hidden = false;
+
+  // Hide urlbar zoom button if zoom is at 100% or the customizable control is
+  // in the toolbar.
+  urlbarZoomButton.hidden =
+    (zoomFactor == 100 ||
+     (customizableZoomControls &&
+      customizableZoomControls.getAttribute("cui-areatype") == "toolbar"));
+
+  let label = win.gNavigatorBundle.getFormattedString("zoom-button.label", [zoomFactor]);
+  if (customizableZoomReset) {
+    customizableZoomReset.setAttribute("label", label);
+  }
+  if (!urlbarZoomButton.hidden) {
     if (aAnimate) {
-      zoomResetButton.setAttribute("animate", "true");
+      urlbarZoomButton.setAttribute("animate", "true");
     } else {
-      zoomResetButton.removeAttribute("animate");
+      urlbarZoomButton.removeAttribute("animate");
     }
-    zoomResetButton.setAttribute("label",
-        win.gNavigatorBundle.getFormattedString("urlbar-zoom-button.label", [zoomFactor]));
-  } else {
-    // Hide button if zoom is at 100%
-    zoomResetButton.hidden = true;
+    urlbarZoomButton.setAttribute("label", label);
   }
 }
 
-Services.obs.addObserver(fullZoomLocationChangeObserver, "browser-fullZoom:location-change", false);
+Components.utils.import("resource:///modules/CustomizableUI.jsm");
+let customizationListener = {
+  onAreaNodeRegistered(aAreaType, aAreaNode) {
+    if (aAreaType == CustomizableUI.AREA_PANEL) {
+      updateZoomUI(aAreaNode.ownerGlobal.gBrowser.selectedBrowser);
+    }
+  }
+};
+customizationListener.onWidgetAdded =
+customizationListener.onWidgetRemoved =
+customizationListener.onWidgetMoved =
+customizationListener.onWidgetInstanceRemoved = function(aWidgetId) {
+  if (aWidgetId == "zoom-controls") {
+    for (let window of CustomizableUI.windows) {
+      updateZoomUI(window.gBrowser.selectedBrowser);
+    }
+  }
+};
+customizationListener.onWidgetReset =
+customizationListener.onWidgetUndoMove = function(aWidgetNode) {
+  if (aWidgetNode.id == "zoom-controls") {
+    updateZoomUI(aWidgetNode.ownerGlobal.gBrowser.selectedBrowser);
+  }
+};
+CustomizableUI.addListener(customizationListener);
+
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -24,33 +24,33 @@ EXTRA_JS_MODULES += [
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
     'DirectoryLinksProvider.jsm',
     'E10SUtils.jsm',
     'ExtensionsUI.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
+    'FullZoomUI.jsm',
     'HiddenFrame.jsm',
     'LaterRun.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PermissionUI.jsm',
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'SocialService.jsm',
     'TransientPrefs.jsm',
-    'URLBarZoom.jsm',
     'webrtcUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',
         'WindowsPreviewPerTab.jsm',
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -916,50 +916,41 @@ notification[value="translation"] menuli
 .autocomplete-richlistitem {
   height: 30px;
   min-height: 30px;
   font: message-box;
   border-radius: 2px;
   border: 1px solid transparent;
 }
 
-.autocomplete-richlistitem[selected=true] {
-  background-color: Highlight;
-}
-
 .ac-title {
   font-size: 1.05em;
 }
 
+.ac-separator,
+.ac-url,
+.ac-action,
 .ac-tags {
   font-size: 0.9em;
 }
 
 html|span.ac-tag {
   background-color: MenuText;
   color: Menu;
   border-radius: 2px;
   border: 1px solid transparent;
   padding: 0 1px;
 }
 
-.ac-separator,
-.ac-url,
-.ac-action {
-  font-size: 0.9em;
+.ac-separator:not([selected=true]),
+.ac-url:not([selected=true]),
+.ac-action:not([selected=true]) {
   color: -moz-nativehyperlinktext;
 }
 
-.ac-title[selected=true],
-.ac-separator[selected],
-.ac-url[selected=true],
-.ac-action[selected=true] {
-  color: inherit !important;
-}
-
 .ac-tags-text[selected] > html|span.ac-tag {
   background-color: HighlightText;
   color: Highlight;
 }
 
 html|span.ac-emphasize-text-title,
 html|span.ac-emphasize-text-tag,
 html|span.ac-emphasize-text-url {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1695,60 +1695,50 @@ toolbar .toolbarbutton-1 > .toolbarbutto
   min-height: 30px;
   font: message-box;
   border-radius: 2px;
   border: 1px solid transparent;
 }
 
 .autocomplete-richlistitem[selected] {
   background-color: hsl(210, 80%, 52%);
+  color: hsl(0, 0%, 100%);
 }
 
 .ac-title {
   font-size: 14px;
-  color: hsl(0, 0%, 0%);
-}
-
+}
+
+.ac-separator,
+.ac-url,
+.ac-action,
 .ac-tags {
   font-size: 12px;
 }
 
 html|span.ac-tag {
   background-color: hsl(216, 0%, 88%);
   color: hsl(0, 0%, 0%);
   border-radius: 2px;
   border: 1px solid transparent;
   padding: 0 1px;
 }
 
-.ac-separator,
-.ac-url,
-.ac-action {
-  font-size: 12px;
-}
-
-.ac-separator {
+.ac-separator:not([selected=true]) {
   color: hsl(0, 0%, 50%);
 }
 
-.ac-url {
+.ac-url:not([selected=true]) {
   color: hsl(210, 77%, 47%);
 }
 
-.ac-action {
+.ac-action:not([selected=true]) {
   color: hsl(178, 100%, 28%);
 }
 
-.ac-title[selected],
-.ac-separator[selected],
-.ac-url[selected],
-.ac-action[selected] {
-  color: hsl(0, 0%, 100%);
-}
-
 .ac-tags-text[selected] > html|span.ac-tag {
   background-color: hsl(0, 0%, 100%);
   color: hsl(210, 80%, 40%);
 }
 
 html|span.ac-emphasize-text-title,
 html|span.ac-emphasize-text-tag,
 html|span.ac-emphasize-text-url {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1406,93 +1406,87 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 .autocomplete-richlistitem {
   height: 30px;
   min-height: 30px;
   font: message-box;
   border-radius: 2px;
   border: 1px solid transparent;
 }
 
-.autocomplete-richlistitem[selected=true] {
-  background-color: hsl(210, 80%, 52%);
-}
-
 .ac-title {
   font-size: 14px;
-  color: hsl(0, 0%, 0%);
 }
 
 .ac-tags {
   font-size: 12px;
 }
 
 html|span.ac-tag {
-  background-color: hsl(216, 0%, 88%);
-  color: hsl(0, 0%, 0%);
   border-radius: 2px;
   border: 1px solid transparent;
   padding: 0 1px;
 }
 
 .ac-separator,
 .ac-url,
 .ac-action {
   font-size: 12px;
 }
 
-.ac-separator {
-  color: hsl(0, 0%, 50%);
-}
-
-.ac-url {
-  color: hsl(210, 77%, 47%);
-}
-
-.ac-action {
-  color: hsl(178, 100%, 28%);
-}
-
-.ac-title[selected=true],
-.ac-separator[selected],
-.ac-url[selected=true],
-.ac-action[selected=true] {
-  color: hsl(0, 0%, 100%);
-}
-
-.ac-tags-text[selected] > html|span.ac-tag {
-  background-color: hsl(0, 0%, 100%);
-  color: hsl(210, 80%, 40%);
-}
-
 html|span.ac-emphasize-text-title,
 html|span.ac-emphasize-text-tag,
 html|span.ac-emphasize-text-url {
   font-weight: 600;
 }
 
-@media (-moz-windows-default-theme: 0) {
+@media (-moz-windows-default-theme) {
   .autocomplete-richlistitem[selected=true] {
-    background-color: Highlight;
+    background-color: hsl(210, 80%, 52%);
+    color: hsl(0, 0%, 100%);
+  }
+
+  .ac-title:not([selected=true]) {
+    color: hsl(0, 0%, 0%);
+  }
+
+  .ac-separator:not([selected=true]) {
+    color: hsl(0, 0%, 50%);
+  }
+
+  .ac-url:not([selected=true]) {
+    color: hsl(210, 77%, 47%);
   }
 
-  .ac-title {
-    color: inherit;
+  .ac-action:not([selected=true]) {
+    color: hsl(178, 100%, 28%);
+  }
+
+  html|span.ac-tag {
+    background-color: hsl(216, 0%, 88%);
+    color: hsl(0, 0%, 0%);
+  }
+
+  .ac-tags-text[selected] > html|span.ac-tag {
+    background-color: hsl(0, 0%, 100%);
+    color: hsl(210, 80%, 40%);
+  }
+}
+
+@media (-moz-windows-default-theme: 0) {
+  .ac-separator:not([selected=true]),
+  .ac-url:not([selected=true]),
+  .ac-action:not([selected=true]) {
+    color: -moz-nativehyperlinktext;
   }
 
   html|span.ac-tag {
     background-color: -moz-FieldText;
     color: -moz-Field;
   }
 
-  .ac-separator,
-  .ac-url,
-  .ac-action {
-    color: -moz-nativehyperlinktext;
-  }
-
   .ac-tags-text[selected] > html|span.ac-tag {
     background-color: HighlightText;
     color: Highlight;
   }
 }
 
 .ac-type-icon[type=bookmark] {
   list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
--- a/build/util/count_ctors.py
+++ b/build/util/count_ctors.py
@@ -1,11 +1,12 @@
 
 #!/usr/bin/python
 import json
+
 import re
 import subprocess
 import sys
 
 def count_ctors(filename):
     proc = subprocess.Popen(
         ['readelf', '-W', '-S', filename], stdout=subprocess.PIPE)
 
@@ -51,14 +52,15 @@ if __name__ == '__main__':
     for f in sys.argv[1:]:
         perfherder_data = {
             "framework": {"name": "build_metrics"},
             "suites": [{
                 "name": "compiler_metrics",
                 "subtests": [{
                     "name": "num_static_constructors",
                     "value": count_ctors(f),
-                    "alertThreshold": 0.25
+                    "alertChangeType": "absolute",
+                    "alertThreshold": 3
                 }]}
             ]
         }
         print "PERFHERDER_DATA: %s" % json.dumps(perfherder_data)
 
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -228,18 +228,18 @@ module.exports = {
     // Disallow unnecessary semicolons.
     "no-extra-semi": "error",
     // Deprecated, will be removed in 1.0.
     "no-extra-strict": "off",
     // Disallow fallthrough of case statements, except if there is a comment.
     "no-fallthrough": "error",
     // Allow the use of leading or trailing decimal points in numeric literals.
     "no-floating-decimal": "off",
-    // Disallow comments inline after code.
-    "no-inline-comments": "error",
+    // Allow comments inline after code.
+    "no-inline-comments": "off",
     // Disallow if as the only statement in an else block.
     "no-lonely-if": "error",
     // Allow mixing regular variable and require declarations (not a node env).
     "no-mixed-requires": "off",
     // Disallow mixed spaces and tabs for indentation.
     "no-mixed-spaces-and-tabs": "error",
     // Disallow use of multiple spaces (sometimes used to align const values,
     // array or object items, etc.). It's hard to maintain and doesn't add that
--- a/devtools/client/commandline/test/browser_cmd_paintflashing.js
+++ b/devtools/client/commandline/test/browser_cmd_paintflashing.js
@@ -11,17 +11,17 @@ const TEST_URI = "http://example.com/bro
 function test() {
   return Task.spawn(testTask).then(finish, helpers.handleError);
 }
 
 var tests = {
   testInput: function (options) {
     let toggleCommand = options.requisition.system.commands.get("paintflashing toggle");
 
-    let _tab = options.tab;
+    let { tab } = options;
 
     let actions = [
       {
         command: "paintflashing on",
         isChecked: true,
         label: "checked after on"
       },
       {
@@ -39,17 +39,17 @@ var tests = {
         isChecked: false,
         label: "unchecked after toggle"
       }
     ];
 
     return helpers.audit(options, actions.map(spec => ({
       setup: spec.command,
       exec: {},
-      post: () => is(toggleCommand.state.isChecked({_tab}), spec.isChecked, spec.label)
+      post: () => is(toggleCommand.state.isChecked({tab}), spec.isChecked, spec.label)
     })));
   },
 };
 
 function* testTask() {
   let options = yield helpers.openTab(TEST_URI);
   yield helpers.openToolbar(options);
 
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -21,16 +21,17 @@ loader.lazyGetter(this, "MemoryPanel", (
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
 loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
 
 // Other dependencies
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
+loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
 loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/startup.properties");
 
 var Tools = {};
 exports.Tools = Tools;
@@ -492,17 +493,25 @@ exports.ToolboxButtons = [
     }
   },
   { id: "command-button-paintflashing",
     description: l10n("toolbox.buttons.paintflashing"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       CommandUtils.executeOnTarget(toolbox.target, "paintflashing toggle");
     },
-    autoToggle: true
+    isChecked(toolbox) {
+      return CommandState.isEnabledForTarget(toolbox.target, "paintflashing");
+    },
+    setup(toolbox, onChange) {
+      CommandState.on("changed", onChange);
+    },
+    teardown(toolbox, onChange) {
+      CommandState.off("changed", onChange);
+    }
   },
   { id: "command-button-scratchpad",
     description: l10n("toolbox.buttons.scratchpad"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       ScratchpadManager.openScratchpad();
     }
   },
@@ -546,25 +555,41 @@ exports.ToolboxButtons = [
     }
   },
   { id: "command-button-rulers",
     description: l10n("toolbox.buttons.rulers"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       CommandUtils.executeOnTarget(toolbox.target, "rulers");
     },
-    autoToggle: true
+    isChecked(toolbox) {
+      return CommandState.isEnabledForTarget(toolbox.target, "rulers");
+    },
+    setup(toolbox, onChange) {
+      CommandState.on("changed", onChange);
+    },
+    teardown(toolbox, onChange) {
+      CommandState.off("changed", onChange);
+    }
   },
   { id: "command-button-measure",
     description: l10n("toolbox.buttons.measure"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       CommandUtils.executeOnTarget(toolbox.target, "measure");
     },
-    autoToggle: true
+    isChecked(toolbox) {
+      return CommandState.isEnabledForTarget(toolbox.target, "measure");
+    },
+    setup(toolbox, onChange) {
+      CommandState.on("changed", onChange);
+    },
+    teardown(toolbox, onChange) {
+      CommandState.off("changed", onChange);
+    }
   },
 ];
 
 /**
  * Lookup l10n string from a string bundle.
  *
  * @param {string} name
  *        The key to lookup.
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -569,35 +569,30 @@ Toolbox.prototype = {
    *                      memory leaks. The same arguments than `setup` function are
    *                      passed to `teardown`.
    * @property {Function} isTargetSupported - Function to automatically enable/disable
    *                      the button based on the target. If the target don't support
    *                      the button feature, this method should return false.
    * @property {Function} isChecked - Optional function called to known if the button
    *                      is toggled or not. The function should return true when
    *                      the button should be displayed as toggled on.
-   * @property {Boolean}  autoToggle - If true, the checked state is going to be
-   *                      automatically toggled on click.
    */
   _createButtonState: function (options) {
     let isCheckedValue = false;
     const { id, className, description, onClick, isInStartContainer, setup, teardown,
-            isTargetSupported, isChecked, autoToggle } = options;
+            isTargetSupported, isChecked } = options;
     const toolbox = this;
     const button = {
       id,
       className,
       description,
       onClick(event) {
         if (typeof onClick == "function") {
           onClick(event, toolbox);
         }
-        if (autoToggle) {
-          button.isChecked = !button.isChecked;
-        }
       },
       isTargetSupported,
       get isChecked() {
         if (typeof isChecked == "function") {
           return isChecked(toolbox);
         }
         return isCheckedValue;
       },
--- a/devtools/client/inspector/test/browser_inspector_search-01.js
+++ b/devtools/client/inspector/test/browser_inspector_search-01.js
@@ -1,12 +1,11 @@
 /* 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-inline-comments: 0 */
 "use strict";
 
 requestLongerTimeout(2);
 
 // Test that searching for nodes in the search field actually selects those
 // nodes.
 
 const TEST_URL = URL_ROOT + "doc_inspector_search.html";
--- a/devtools/client/netmonitor/components/request-list-header.js
+++ b/devtools/client/netmonitor/components/request-list-header.js
@@ -9,20 +9,18 @@ const { div, button } = DOM;
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
 const { L10N } = require("../utils/l10n");
 const { getWaterfallScale } = require("../selectors/index");
 const Actions = require("../actions/index");
 const WaterfallBackground = require("../waterfall-background");
 const { getFormattedTime } = require("../utils/format-utils");
 
-// ms
-const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
-// px
-const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
+const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
+const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
 
 const HEADERS = [
   { name: "status", label: "status3" },
   { name: "method" },
   { name: "file", boxName: "icon-and-file" },
   { name: "domain", boxName: "security-and-domain" },
   { name: "cause" },
   { name: "type" },
--- a/devtools/client/netmonitor/middleware/batching.js
+++ b/devtools/client/netmonitor/middleware/batching.js
@@ -1,18 +1,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/. */
 
 "use strict";
 
 const { BATCH_ACTIONS, BATCH_ENABLE, BATCH_RESET } = require("../constants");
 
-// ms
-const REQUESTS_REFRESH_RATE = 50;
+const REQUESTS_REFRESH_RATE = 50; // ms
 
 /**
  * Middleware that watches for actions with a "batch = true" value in their meta field.
  * These actions are queued and dispatched as one batch after a timeout.
  * Special actions that are handled by this middleware:
  * - BATCH_ENABLE can be used to enable and disable the batching.
  * - BATCH_RESET discards the actions that are currently in the queue.
  */
--- a/devtools/client/netmonitor/request-list-tooltip.js
+++ b/devtools/client/netmonitor/request-list-tooltip.js
@@ -6,20 +6,18 @@
 
 const {
   setImageTooltip,
   getImageDimensions,
 } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const { WEBCONSOLE_L10N } = require("./utils/l10n");
 const { formDataURI } = require("./utils/request-utils");
 
-// px
-const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
-// px
-const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
+const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400; // px
+const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600; // px
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 async function setTooltipImageContent(tooltip, itemEl, requestItem) {
   let { mimeType, text, encoding } = requestItem.responseContent.content;
 
   if (!mimeType || !mimeType.includes("image/")) {
     return false;
--- a/devtools/client/netmonitor/waterfall-background.js
+++ b/devtools/client/netmonitor/waterfall-background.js
@@ -1,20 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
-// ms
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5;
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
-// px
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10;
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
 // 8-bit value of the alpha component of the tick color
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32;
 const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32;
 // RGBA colors for the timing markers
 const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [0, 0, 255, 128];
 const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [255, 0, 0, 128];
 
--- a/devtools/client/performance/components/waterfall-header.js
+++ b/devtools/client/performance/components/waterfall-header.js
@@ -7,22 +7,19 @@
 /**
  * The "waterfall ticks" view, a header for the markers displayed in the waterfall.
  */
 
 const { DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../modules/global");
 const { TickUtils } = require("../modules/waterfall-ticks");
 
-// ms
-const WATERFALL_HEADER_TICKS_MULTIPLE = 5;
-// px
-const WATERFALL_HEADER_TICKS_SPACING_MIN = 50;
-// px
-const WATERFALL_HEADER_TEXT_PADDING = 3;
+const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
+const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
+const WATERFALL_HEADER_TEXT_PADDING = 3; // px
 
 function WaterfallHeader(props) {
   let { startTime, dataScale, sidebarWidth, waterfallWidth } = props;
 
   let tickInterval = TickUtils.findOptimalTickInterval({
     ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
     ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
     dataScale: dataScale
--- a/devtools/client/performance/components/waterfall-tree-row.js
+++ b/devtools/client/performance/components/waterfall-tree-row.js
@@ -6,22 +6,19 @@
 
 /**
  * A single row (node) in the waterfall tree
  */
 
 const { DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
 const { MarkerBlueprintUtils } = require("../modules/marker-blueprint-utils");
 
-// px
-const LEVEL_INDENT = 10;
-// px
-const ARROW_NODE_OFFSET = -14;
-// px
-const WATERFALL_MARKER_TIMEBAR_WIDTH_MIN = 5;
+const LEVEL_INDENT = 10; // px
+const ARROW_NODE_OFFSET = -14; // px
+const WATERFALL_MARKER_TIMEBAR_WIDTH_MIN = 5; // px
 
 function buildMarkerSidebar(blueprint, props) {
   const { marker, level, sidebarWidth } = props;
 
   let bullet = dom.div({
     className: `waterfall-marker-bullet marker-color-${blueprint.colorName}`,
     style: { transform: `translateX(${level * LEVEL_INDENT}px)` },
     "data-type": marker.name
--- a/devtools/client/performance/components/waterfall-tree.js
+++ b/devtools/client/performance/components/waterfall-tree.js
@@ -2,18 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
 const Tree = createFactory(require("devtools/client/shared/components/tree"));
 const WaterfallTreeRow = createFactory(require("./waterfall-tree-row"));
 
-// px - keep in sync with var(--waterfall-tree-row-height) in performance.css
-const WATERFALL_TREE_ROW_HEIGHT = 15;
+// Keep in sync with var(--waterfall-tree-row-height) in performance.css
+const WATERFALL_TREE_ROW_HEIGHT = 15; // px
 
 /**
  * Checks if a given marker is in the specified time range.
  *
  * @param object e
  *        The marker containing the { start, end } timestamps.
  * @param number start
  *        The earliest allowed time.
--- a/devtools/client/performance/modules/waterfall-ticks.js
+++ b/devtools/client/performance/modules/waterfall-ticks.js
@@ -1,25 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
-// ms
-const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5;
+const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
 const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
-// px
-const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10;
+const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
 const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
-// byte
-const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32;
-// byte
-const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32;
+const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
+const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
 
 const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
 
 /**
  * Creates the background displayed on the marker's waterfall.
  */
 function drawWaterfallBackground(doc, dataScale, waterfallWidth) {
   let canvas = doc.createElementNS(HTML_NS, "canvas");
--- a/devtools/client/performance/modules/widgets/graphs.js
+++ b/devtools/client/performance/modules/widgets/graphs.js
@@ -20,36 +20,31 @@ const { colorUtils } = require("devtools
 const { getColor } = require("devtools/client/shared/theme");
 const ProfilerGlobal = require("devtools/client/performance/modules/global");
 const { MarkersOverview } = require("devtools/client/performance/modules/widgets/markers-overview");
 const { createTierGraphDataFromFrameNode } = require("devtools/client/performance/modules/logic/jit");
 
 /**
  * For line graphs
  */
-// px
-const HEIGHT = 35;
-// px
-const STROKE_WIDTH = 1;
+const HEIGHT = 35; // px
+const STROKE_WIDTH = 1; // px
 const DAMPEN_VALUES = 0.95;
 const CLIPHEAD_LINE_COLOR = "#666";
 const SELECTION_LINE_COLOR = "#555";
 const SELECTION_BACKGROUND_COLOR_NAME = "graphs-blue";
 const FRAMERATE_GRAPH_COLOR_NAME = "graphs-green";
 const MEMORY_GRAPH_COLOR_NAME = "graphs-blue";
 
 /**
  * For timeline overview
  */
-// px
-const MARKERS_GRAPH_HEADER_HEIGHT = 14;
-// px
-const MARKERS_GRAPH_ROW_HEIGHT = 10;
-// px
-const MARKERS_GROUP_VERTICAL_PADDING = 4;
+const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
+const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
+const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
 
 /**
  * For optimization graph
  */
 const OPTIMIZATIONS_GRAPH_RESOLUTION = 100;
 
 /**
  * A base class for performance graphs to inherit from.
--- a/devtools/client/performance/modules/widgets/markers-overview.js
+++ b/devtools/client/performance/modules/widgets/markers-overview.js
@@ -14,39 +14,30 @@ const { AbstractCanvasGraph } = require(
 
 const { colorUtils } = require("devtools/shared/css/color");
 const { getColor } = require("devtools/client/shared/theme");
 const ProfilerGlobal = require("devtools/client/performance/modules/global");
 const { MarkerBlueprintUtils } = require("devtools/client/performance/modules/marker-blueprint-utils");
 const { TickUtils } = require("devtools/client/performance/modules/waterfall-ticks");
 const { TIMELINE_BLUEPRINT } = require("devtools/client/performance/modules/markers");
 
-// px
-const OVERVIEW_HEADER_HEIGHT = 14;
-// px
-const OVERVIEW_ROW_HEIGHT = 11;
+const OVERVIEW_HEADER_HEIGHT = 14; // px
+const OVERVIEW_ROW_HEIGHT = 11; // px
 
 const OVERVIEW_SELECTION_LINE_COLOR = "#666";
 const OVERVIEW_CLIPHEAD_LINE_COLOR = "#555";
 
-// ms
-const OVERVIEW_HEADER_TICKS_MULTIPLE = 100;
-// px
-const OVERVIEW_HEADER_TICKS_SPACING_MIN = 75;
-// px
-const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9;
+const OVERVIEW_HEADER_TICKS_MULTIPLE = 100; // ms
+const OVERVIEW_HEADER_TICKS_SPACING_MIN = 75; // px
+const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px
 const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif";
-// px
-const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6;
-// px
-const OVERVIEW_HEADER_TEXT_PADDING_TOP = 1;
-// px
-const OVERVIEW_MARKER_WIDTH_MIN = 4;
-// px
-const OVERVIEW_GROUP_VERTICAL_PADDING = 5;
+const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px
+const OVERVIEW_HEADER_TEXT_PADDING_TOP = 1; // px
+const OVERVIEW_MARKER_WIDTH_MIN = 4; // px
+const OVERVIEW_GROUP_VERTICAL_PADDING = 5; // px
 
 /**
  * An overview for the markers data.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the overview.
  * @param Array<String> filter
  *        List of names of marker types that should not be shown.
--- a/devtools/client/performance/modules/widgets/tree-view.js
+++ b/devtools/client/performance/modules/widgets/tree-view.js
@@ -10,18 +10,17 @@
 
 const { L10N } = require("devtools/client/performance/modules/global");
 const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
 const { AbstractTreeItem } = require("resource://devtools/client/shared/widgets/AbstractTreeItem.jsm");
 
 const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
 const VIEW_OPTIMIZATIONS_TOOLTIP = L10N.getStr("table.view-optimizations.tooltiptext2");
 
-// px
-const CALL_TREE_INDENTATION = 16;
+const CALL_TREE_INDENTATION = 16; // px
 
 // Used for rendering values in cells
 const FORMATTERS = {
   TIME: (value) => L10N.getFormatStr("table.ms2", L10N.numberWithDecimals(value, 2)),
   PERCENT: (value) => L10N.getFormatStr("table.percentage3",
                                         L10N.numberWithDecimals(value, 2)),
   NUMBER: (value) => value || 0,
   BYTESIZE: (value) => L10N.getFormatStr("table.bytes", (value || 0))
--- a/devtools/client/performance/test/unit/test_perf-utils-allocations-to-samples.js
+++ b/devtools/client/performance/test/unit/test_perf-utils-allocations-to-samples.js
@@ -40,17 +40,16 @@ var TEST_DATA = {
       line: 5,
       column: 6,
       functionDisplayName: null,
       parent: 2
     }
   ]
 };
 
-/* eslint-disable no-inline-comments */
 var EXPECTED_OUTPUT = {
   name: "allocations",
   samples: {
     "schema": {
       "stack": 0,
       "time": 1,
       "size": 2,
     },
@@ -88,9 +87,8 @@ var EXPECTED_OUTPUT = {
     ]
   },
   "stringTable": [
     "x (A:1:2)",
     "y (B:3:4)",
     "C:5:6"
   ],
 };
-/* eslint-enable no-inline-comments */
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.jsm
@@ -1,33 +1,33 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const Ci = Components.interfaces;
+"use strict";
+
 const Cu = Components.utils;
 
-var {loader, require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var Telemetry = require("devtools/client/shared/telemetry");
-var {showDoorhanger} = require("devtools/client/shared/doorhanger");
-var {TouchEventSimulator} = require("devtools/shared/touch/simulator");
-var {Task} = require("devtools/shared/task");
-var promise = require("promise");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var flags = require("devtools/shared/flags");
-var Services = require("Services");
-var EventEmitter = require("devtools/shared/event-emitter");
-var {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
-var { LocalizationHelper } = require("devtools/shared/l10n");
-var { EmulationFront } = require("devtools/shared/fronts/emulation");
+const { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { LocalizationHelper } = require("devtools/shared/l10n");
+const { Task } = require("devtools/shared/task");
+const Services = require("Services");
+const EventEmitter = require("devtools/shared/event-emitter");
 
 loader.lazyImporter(this, "SystemAppProxy",
                     "resource://gre/modules/SystemAppProxy.jsm");
+loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
+loader.lazyRequireGetter(this, "showDoorhanger",
+                         "devtools/client/shared/doorhanger", true);
+loader.lazyRequireGetter(this, "TouchEventSimulator",
+                         "devtools/shared/touch/simulator", true);
+loader.lazyRequireGetter(this, "flags",
+                         "devtools/shared/flags");
+loader.lazyRequireGetter(this, "EmulationFront",
+                         "devtools/shared/fronts/emulation", true);
 loader.lazyRequireGetter(this, "DebuggerClient",
                          "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "DebuggerServer",
                          "devtools/server/main", true);
 
 this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
 
 const MIN_WIDTH = 50;
@@ -50,85 +50,86 @@ function debug(msg) {
 var ActiveTabs = new Map();
 
 var Manager = {
   /**
    * Check if the a tab is in a responsive mode.
    * Leave the responsive mode if active,
    * active the responsive mode if not active.
    *
-   * @param aWindow the main window.
-   * @param aTab the tab targeted.
+   * @param window the main window.
+   * @param tab the tab targeted.
    */
-  toggle: function (aWindow, aTab) {
-    if (this.isActiveForTab(aTab)) {
-      ActiveTabs.get(aTab).close();
+  toggle: function (window, tab) {
+    if (this.isActiveForTab(tab)) {
+      ActiveTabs.get(tab).close();
     } else {
-      this.openIfNeeded(aWindow, aTab);
+      this.openIfNeeded(window, tab);
     }
   },
 
   /**
    * Launches the responsive mode.
    *
-   * @param aWindow the main window.
-   * @param aTab the tab targeted.
+   * @param window the main window.
+   * @param tab the tab targeted.
    * @returns {ResponsiveUI} the instance of ResponsiveUI for the current tab.
    */
-  openIfNeeded: Task.async(function* (aWindow, aTab) {
+  openIfNeeded: Task.async(function* (window, tab) {
     let ui;
-    if (!this.isActiveForTab(aTab)) {
-      ui = new ResponsiveUI(aWindow, aTab);
+    if (!this.isActiveForTab(tab)) {
+      ui = new ResponsiveUI(window, tab);
       yield ui.inited;
     } else {
-      ui = this.getResponsiveUIForTab(aTab);
+      ui = this.getResponsiveUIForTab(tab);
     }
     return ui;
   }),
 
   /**
    * Returns true if responsive view is active for the provided tab.
    *
-   * @param aTab the tab targeted.
+   * @param tab the tab targeted.
    */
-  isActiveForTab: function (aTab) {
-    return ActiveTabs.has(aTab);
+  isActiveForTab: function (tab) {
+    return ActiveTabs.has(tab);
   },
 
   /**
    * Return the responsive UI controller for a tab.
    */
-  getResponsiveUIForTab: function (aTab) {
-    return ActiveTabs.get(aTab);
+  getResponsiveUIForTab: function (tab) {
+    return ActiveTabs.get(tab);
   },
 
   /**
    * Handle gcli commands.
    *
-   * @param aWindow the browser window.
-   * @param aTab the tab targeted.
-   * @param aCommand the command name.
-   * @param aArgs command arguments.
+   * @param window the browser window.
+   * @param tab the tab targeted.
+   * @param command the command name.
+   * @param args command arguments.
    */
-  handleGcliCommand: Task.async(function* (aWindow, aTab, aCommand, aArgs) {
-    switch (aCommand) {
+  handleGcliCommand: Task.async(function* (window, tab, command, args) {
+    switch (command) {
       case "resize to":
-        let ui = yield this.openIfNeeded(aWindow, aTab);
-        ui.setViewportSize(aArgs);
+        let ui = yield this.openIfNeeded(window, tab);
+        ui.setViewportSize(args);
         break;
       case "resize on":
-        this.openIfNeeded(aWindow, aTab);
+        this.openIfNeeded(window, tab);
         break;
       case "resize off":
-        if (this.isActiveForTab(aTab)) {
-          yield ActiveTabs.get(aTab).close();
+        if (this.isActiveForTab(tab)) {
+          yield ActiveTabs.get(tab).close();
         }
         break;
       case "resize toggle":
-        this.toggle(aWindow, aTab);
+        this.toggle(window, tab);
+        break;
       default:
     }
   })
 };
 
 EventEmitter.decorate(Manager);
 
 // If the new HTML RDM UI is enabled and e10s is enabled by default (e10s is required for
@@ -141,60 +142,59 @@ if (Services.prefs.getBoolPref("devtools
     require("devtools/client/responsive.html/manager");
   this.ResponsiveUIManager = ResponsiveUIManager;
 } else {
   this.ResponsiveUIManager = Manager;
 }
 
 var defaultPresets = [
   // Phones
-  {key: "320x480", width: 320, height: 480},    // iPhone, B2G, with <meta viewport>
-  {key: "360x640", width: 360, height: 640},    // Android 4, phones, with <meta viewport>
+  {key: "320x480", width: 320, height: 480},   // iPhone, B2G, with <meta viewport>
+  {key: "360x640", width: 360, height: 640},   // Android 4, phones, with <meta viewport>
 
   // Tablets
-  {key: "768x1024", width: 768, height: 1024},   // iPad, with <meta viewport>
-  {key: "800x1280", width: 800, height: 1280},   // Android 4, Tablet, with <meta viewport>
+  {key: "768x1024", width: 768, height: 1024}, // iPad, with <meta viewport>
+  {key: "800x1280", width: 800, height: 1280}, // Android 4, Tablet, with <meta viewport>
 
   // Default width for mobile browsers, no <meta viewport>
   {key: "980x1280", width: 980, height: 1280},
 
   // Computer
   {key: "1280x600", width: 1280, height: 600},
   {key: "1920x900", width: 1920, height: 900},
 ];
 
-function ResponsiveUI(aWindow, aTab)
-{
-  this.mainWindow = aWindow;
-  this.tab = aTab;
+function ResponsiveUI(window, tab) {
+  this.mainWindow = window;
+  this.tab = tab;
   this.mm = this.tab.linkedBrowser.messageManager;
-  this.tabContainer = aWindow.gBrowser.tabContainer;
-  this.browser = aTab.linkedBrowser;
-  this.chromeDoc = aWindow.document;
-  this.container = aWindow.gBrowser.getBrowserContainer(this.browser);
+  this.tabContainer = window.gBrowser.tabContainer;
+  this.browser = tab.linkedBrowser;
+  this.chromeDoc = window.document;
+  this.container = window.gBrowser.getBrowserContainer(this.browser);
   this.stack = this.container.querySelector(".browserStack");
   this._telemetry = new Telemetry();
 
   // Let's bind some callbacks.
-  this.bound_presetSelected = this.presetSelected.bind(this);
-  this.bound_handleManualInput = this.handleManualInput.bind(this);
-  this.bound_addPreset = this.addPreset.bind(this);
-  this.bound_removePreset = this.removePreset.bind(this);
-  this.bound_rotate = this.rotate.bind(this);
-  this.bound_screenshot = () => this.screenshot();
-  this.bound_touch = this.toggleTouch.bind(this);
-  this.bound_close = this.close.bind(this);
-  this.bound_startResizing = this.startResizing.bind(this);
-  this.bound_stopResizing = this.stopResizing.bind(this);
-  this.bound_onDrag = this.onDrag.bind(this);
-  this.bound_changeUA = this.changeUA.bind(this);
-  this.bound_onContentResize = this.onContentResize.bind(this);
+  this.boundPresetSelected = this.presetSelected.bind(this);
+  this.boundHandleManualInput = this.handleManualInput.bind(this);
+  this.boundAddPreset = this.addPreset.bind(this);
+  this.boundRemovePreset = this.removePreset.bind(this);
+  this.boundRotate = this.rotate.bind(this);
+  this.boundScreenshot = () => this.screenshot();
+  this.boundTouch = this.toggleTouch.bind(this);
+  this.boundClose = this.close.bind(this);
+  this.boundStartResizing = this.startResizing.bind(this);
+  this.boundStopResizing = this.stopResizing.bind(this);
+  this.boundOnDrag = this.onDrag.bind(this);
+  this.boundChangeUA = this.changeUA.bind(this);
+  this.boundOnContentResize = this.onContentResize.bind(this);
 
   this.mm.addMessageListener("ResponsiveMode:OnContentResize",
-                             this.bound_onContentResize);
+                             this.boundOnContentResize);
 
   // We must be ready to handle window or tab close now that we have saved
   // ourselves in ActiveTabs.  Otherwise we risk leaking the window.
   this.mainWindow.addEventListener("unload", this);
   this.tab.addEventListener("TabClose", this);
   this.tabContainer.addEventListener("TabSelect", this);
 
   ActiveTabs.set(this.tab, this);
@@ -202,21 +202,21 @@ function ResponsiveUI(aWindow, aTab)
   this.inited = this.init();
 }
 
 ResponsiveUI.prototype = {
   _transitionsEnabled: true,
   get transitionsEnabled() {
     return this._transitionsEnabled;
   },
-  set transitionsEnabled(aValue) {
-    this._transitionsEnabled = aValue;
-    if (aValue && !this._resizing && this.stack.hasAttribute("responsivemode")) {
+  set transitionsEnabled(value) {
+    this._transitionsEnabled = value;
+    if (value && !this._resizing && this.stack.hasAttribute("responsivemode")) {
       this.stack.removeAttribute("notransition");
-    } else if (!aValue) {
+    } else if (!value) {
       this.stack.setAttribute("notransition", "true");
     }
   },
 
   init: Task.async(function* () {
     debug("INIT BEGINS");
     let ready = this.waitForMessage("ResponsiveMode:ChildScriptReady");
     this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
@@ -242,17 +242,19 @@ ResponsiveUI.prototype = {
     this.buildUI();
     this.checkMenus();
 
     // Rotate the responsive mode if needed
     try {
       if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
         this.rotate();
       }
-    } catch (e) {}
+    } catch (e) {
+      // There is no default value defined, so errors are expected.
+    }
 
     // Touch events support
     this.touchEnableBefore = false;
     this.touchEventSimulator = new TouchEventSimulator(this.browser);
 
     yield this.connectToServer();
     this.userAgentInput.hidden = false;
 
@@ -271,49 +273,50 @@ ResponsiveUI.prototype = {
 
   connectToServer: Task.async(function* () {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     this.client = new DebuggerClient(DebuggerServer.connectPipe());
     yield this.client.connect();
-    let {tab} = yield this.client.getTab();
+    let { tab } = yield this.client.getTab();
     yield this.client.attachTab(tab.actor);
     this.emulationFront = EmulationFront(this.client, tab);
   }),
 
   loadPresets: function () {
     // Try to load presets from prefs
     let presets = defaultPresets;
     if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
       try {
         presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
       } catch (e) {
         // User pref is malformated.
         console.error("Could not parse pref `devtools.responsiveUI.presets`: " + e);
       }
     }
 
-    this.customPreset = {key: "custom", custom: true};
+    this.customPreset = { key: "custom", custom: true };
 
     if (Array.isArray(presets)) {
       this.presets = [this.customPreset].concat(presets);
     } else {
       console.error("Presets value (devtools.responsiveUI.presets) is malformated.");
       this.presets = [this.customPreset];
     }
 
     try {
       let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
       let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
       this.customPreset.width = Math.min(MAX_WIDTH, width);
       this.customPreset.height = Math.min(MAX_HEIGHT, height);
 
-      this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
+      this.currentPresetKey =
+        Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
     } catch (e) {
       // Default size. The first preset (custom) is the one that will be used.
       let bbox = this.stack.getBoundingClientRect();
 
       this.customPreset.width = bbox.width - 40; // horizontal padding of the container
       this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
 
       this.currentPresetKey = this.presets[1].key; // most common preset
@@ -347,32 +350,33 @@ ResponsiveUI.prototype = {
     this.stack.setAttribute("style", style);
 
     // Wait for resize message before stopping in the child when testing,
     // but only if we should expect to still get a message.
     if (flags.testing && this.tab.linkedBrowser.messageManager) {
       yield this.waitForMessage("ResponsiveMode:OnContentResize");
     }
 
-    if (this.isResizing)
+    if (this.isResizing) {
       this.stopResizing();
+    }
 
     // Remove listeners.
-    this.menulist.removeEventListener("select", this.bound_presetSelected, true);
-    this.menulist.removeEventListener("change", this.bound_handleManualInput, true);
+    this.menulist.removeEventListener("select", this.boundPresetSelected, true);
+    this.menulist.removeEventListener("change", this.boundHandleManualInput, true);
     this.mainWindow.removeEventListener("unload", this);
     this.tab.removeEventListener("TabClose", this);
     this.tabContainer.removeEventListener("TabSelect", this);
-    this.rotatebutton.removeEventListener("command", this.bound_rotate, true);
-    this.screenshotbutton.removeEventListener("command", this.bound_screenshot, true);
-    this.closebutton.removeEventListener("command", this.bound_close, true);
-    this.addbutton.removeEventListener("command", this.bound_addPreset, true);
-    this.removebutton.removeEventListener("command", this.bound_removePreset, true);
-    this.touchbutton.removeEventListener("command", this.bound_touch, true);
-    this.userAgentInput.removeEventListener("blur", this.bound_changeUA, true);
+    this.rotatebutton.removeEventListener("command", this.boundRotate, true);
+    this.screenshotbutton.removeEventListener("command", this.boundScreenshot, true);
+    this.closebutton.removeEventListener("command", this.boundClose, true);
+    this.addbutton.removeEventListener("command", this.boundAddPreset, true);
+    this.removebutton.removeEventListener("command", this.boundRemovePreset, true);
+    this.touchbutton.removeEventListener("command", this.boundTouch, true);
+    this.userAgentInput.removeEventListener("blur", this.boundChangeUA, true);
 
     // Removed elements.
     this.container.removeChild(this.toolbar);
     if (this.bottomToolbar) {
       this.bottomToolbar.remove();
       delete this.bottomToolbar;
     }
     this.stack.removeChild(this.resizer);
@@ -424,18 +428,18 @@ ResponsiveUI.prototype = {
       width: msg.data.width,
       height: msg.data.height,
     });
   },
 
   /**
    * Handle events
    */
-  handleEvent: function (aEvent) {
-    switch (aEvent.type) {
+  handleEvent: function (event) {
+    switch (event.type) {
       case "TabClose":
       case "unload":
         this.close();
         break;
       case "TabSelect":
         if (this.tab.selected) {
           this.checkMenus();
         } else if (!this.mainWindow.gBrowser.selectedTab.responsiveUI) {
@@ -447,39 +451,42 @@ ResponsiveUI.prototype = {
 
   getViewportBrowser() {
     return this.browser;
   },
 
   /**
    * Check the menu items.
    */
-  checkMenus: function RUI_checkMenus() {
+  checkMenus: function () {
     this.chromeDoc.getElementById("menu_responsiveUI").setAttribute("checked", "true");
   },
 
   /**
    * Uncheck the menu items.
    */
-  unCheckMenus: function RUI_unCheckMenus() {
+  unCheckMenus: function () {
     let el = this.chromeDoc.getElementById("menu_responsiveUI");
     if (el) {
       el.setAttribute("checked", "false");
     }
   },
 
   /**
    * Build the toolbar and the resizers.
    *
    * <vbox class="browserContainer"> From tabbrowser.xml
    *  <toolbar class="devtools-responsiveui-toolbar">
    *    <menulist class="devtools-responsiveui-menulist"/> // presets
-   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="rotate"/> // rotate
-   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="screenshot"/> // screenshot
-   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="Leave Responsive Design Mode"/> // close
+   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton"
+   *                   tooltiptext="rotate"/> // rotate
+   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton"
+   *                   tooltiptext="screenshot"/> // screenshot
+   *    <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton"
+   *                   tooltiptext="Leave Responsive Design Mode"/> // close
    *  </toolbar>
    *  <stack class="browserStack"> From tabbrowser.xml
    *    <browser/>
    *    <box class="devtools-responsiveui-resizehandle" bottom="0" right="0"/>
    *    <box class="devtools-responsiveui-resizebarV" top="0" right="0"/>
    *    <box class="devtools-responsiveui-resizebarH" bottom="0" left="0"/>
    *    // Additional button in FxOS mode:
    *    <button class="devtools-responsiveui-sleep-button" />
@@ -488,180 +495,203 @@ ResponsiveUI.prototype = {
    *      <button class="devtools-responsiveui-volume-down-button" />
    *    </vbox>
    *  </stack>
    *  <toolbar class="devtools-responsiveui-hardware-button">
    *    <toolbarbutton class="devtools-responsiveui-home-button" />
    *  </toolbar>
    * </vbox>
    */
-  buildUI: function RUI_buildUI() {
+  buildUI: function () {
     // Toolbar
     this.toolbar = this.chromeDoc.createElement("toolbar");
     this.toolbar.className = "devtools-responsiveui-toolbar";
     this.toolbar.setAttribute("fullscreentoolbar", "true");
 
     this.menulist = this.chromeDoc.createElement("menulist");
     this.menulist.className = "devtools-responsiveui-menulist";
     this.menulist.setAttribute("editable", "true");
 
-    this.menulist.addEventListener("select", this.bound_presetSelected, true);
-    this.menulist.addEventListener("change", this.bound_handleManualInput, true);
+    this.menulist.addEventListener("select", this.boundPresetSelected, true);
+    this.menulist.addEventListener("change", this.boundHandleManualInput, true);
 
     this.menuitems = new Map();
 
     let menupopup = this.chromeDoc.createElement("menupopup");
     this.registerPresets(menupopup);
     this.menulist.appendChild(menupopup);
 
     this.addbutton = this.chromeDoc.createElement("menuitem");
-    this.addbutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.addPreset"));
-    this.addbutton.addEventListener("command", this.bound_addPreset, true);
+    this.addbutton.setAttribute(
+      "label",
+      this.strings.GetStringFromName("responsiveUI.addPreset")
+    );
+    this.addbutton.addEventListener("command", this.boundAddPreset, true);
 
     this.removebutton = this.chromeDoc.createElement("menuitem");
-    this.removebutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.removePreset"));
-    this.removebutton.addEventListener("command", this.bound_removePreset, true);
+    this.removebutton.setAttribute(
+      "label",
+      this.strings.GetStringFromName("responsiveUI.removePreset")
+    );
+    this.removebutton.addEventListener("command", this.boundRemovePreset, true);
 
     menupopup.appendChild(this.chromeDoc.createElement("menuseparator"));
     menupopup.appendChild(this.addbutton);
     menupopup.appendChild(this.removebutton);
 
     this.rotatebutton = this.chromeDoc.createElement("toolbarbutton");
     this.rotatebutton.setAttribute("tabindex", "0");
-    this.rotatebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.rotate2"));
-    this.rotatebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-rotate";
-    this.rotatebutton.addEventListener("command", this.bound_rotate, true);
+    this.rotatebutton.setAttribute(
+      "tooltiptext",
+      this.strings.GetStringFromName("responsiveUI.rotate2")
+    );
+    this.rotatebutton.className =
+      "devtools-responsiveui-toolbarbutton devtools-responsiveui-rotate";
+    this.rotatebutton.addEventListener("command", this.boundRotate, true);
 
     this.screenshotbutton = this.chromeDoc.createElement("toolbarbutton");
     this.screenshotbutton.setAttribute("tabindex", "0");
-    this.screenshotbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.screenshot"));
-    this.screenshotbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot";
-    this.screenshotbutton.addEventListener("command", this.bound_screenshot, true);
+    this.screenshotbutton.setAttribute(
+      "tooltiptext",
+      this.strings.GetStringFromName("responsiveUI.screenshot")
+    );
+    this.screenshotbutton.className =
+      "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot";
+    this.screenshotbutton.addEventListener("command", this.boundScreenshot, true);
 
     this.closebutton = this.chromeDoc.createElement("toolbarbutton");
     this.closebutton.setAttribute("tabindex", "0");
-    this.closebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-close";
-    this.closebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.close1"));
-    this.closebutton.addEventListener("command", this.bound_close, true);
+    this.closebutton.className =
+      "devtools-responsiveui-toolbarbutton devtools-responsiveui-close";
+    this.closebutton.setAttribute(
+      "tooltiptext",
+      this.strings.GetStringFromName("responsiveUI.close1")
+    );
+    this.closebutton.addEventListener("command", this.boundClose, true);
 
     this.toolbar.appendChild(this.closebutton);
     this.toolbar.appendChild(this.menulist);
     this.toolbar.appendChild(this.rotatebutton);
 
     this.touchbutton = this.chromeDoc.createElement("toolbarbutton");
     this.touchbutton.setAttribute("tabindex", "0");
-    this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch"));
-    this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
-    this.touchbutton.addEventListener("command", this.bound_touch, true);
+    this.touchbutton.setAttribute(
+      "tooltiptext",
+      this.strings.GetStringFromName("responsiveUI.touch")
+    );
+    this.touchbutton.className =
+      "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
+    this.touchbutton.addEventListener("command", this.boundTouch, true);
     this.toolbar.appendChild(this.touchbutton);
 
     this.toolbar.appendChild(this.screenshotbutton);
 
     this.userAgentInput = this.chromeDoc.createElement("textbox");
     this.userAgentInput.className = "devtools-responsiveui-textinput";
     this.userAgentInput.setAttribute("placeholder",
       this.strings.GetStringFromName("responsiveUI.userAgentPlaceholder"));
-    this.userAgentInput.addEventListener("blur", this.bound_changeUA, true);
+    this.userAgentInput.addEventListener("blur", this.boundChangeUA, true);
     this.userAgentInput.hidden = true;
     this.toolbar.appendChild(this.userAgentInput);
 
     // Resizers
     let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip");
     this.resizer = this.chromeDoc.createElement("box");
     this.resizer.className = "devtools-responsiveui-resizehandle";
     this.resizer.setAttribute("right", "0");
     this.resizer.setAttribute("bottom", "0");
     this.resizer.setAttribute("tooltiptext", resizerTooltip);
-    this.resizer.onmousedown = this.bound_startResizing;
+    this.resizer.onmousedown = this.boundStartResizing;
 
     this.resizeBarV = this.chromeDoc.createElement("box");
     this.resizeBarV.className = "devtools-responsiveui-resizebarV";
     this.resizeBarV.setAttribute("top", "0");
     this.resizeBarV.setAttribute("right", "0");
     this.resizeBarV.setAttribute("tooltiptext", resizerTooltip);
-    this.resizeBarV.onmousedown = this.bound_startResizing;
+    this.resizeBarV.onmousedown = this.boundStartResizing;
 
     this.resizeBarH = this.chromeDoc.createElement("box");
     this.resizeBarH.className = "devtools-responsiveui-resizebarH";
     this.resizeBarH.setAttribute("bottom", "0");
     this.resizeBarH.setAttribute("left", "0");
     this.resizeBarH.setAttribute("tooltiptext", resizerTooltip);
-    this.resizeBarH.onmousedown = this.bound_startResizing;
+    this.resizeBarH.onmousedown = this.boundStartResizing;
 
     this.container.insertBefore(this.toolbar, this.stack);
     this.stack.appendChild(this.resizer);
     this.stack.appendChild(this.resizeBarV);
     this.stack.appendChild(this.resizeBarH);
   },
 
   // FxOS custom controls
   buildPhoneUI: function () {
     this.stack.classList.add("fxos-mode");
 
     let sleepButton = this.chromeDoc.createElement("button");
     sleepButton.className = "devtools-responsiveui-sleep-button";
     sleepButton.setAttribute("top", 0);
     sleepButton.setAttribute("right", 0);
     sleepButton.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", {key: "Power"});
+      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "Power" });
     });
     sleepButton.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", {key: "Power"});
+      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "Power" });
     });
     this.stack.appendChild(sleepButton);
 
     let volumeButtons = this.chromeDoc.createElement("vbox");
     volumeButtons.className = "devtools-responsiveui-volume-buttons";
     volumeButtons.setAttribute("top", 0);
     volumeButtons.setAttribute("left", 0);
 
     let volumeUp = this.chromeDoc.createElement("button");
     volumeUp.className = "devtools-responsiveui-volume-up-button";
     volumeUp.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", {key: "AudioVolumeUp"});
+      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "AudioVolumeUp" });
     });
     volumeUp.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", {key: "AudioVolumeUp"});
+      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "AudioVolumeUp" });
     });
 
     let volumeDown = this.chromeDoc.createElement("button");
     volumeDown.className = "devtools-responsiveui-volume-down-button";
     volumeDown.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", {key: "AudioVolumeDown"});
+      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "AudioVolumeDown" });
     });
     volumeDown.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", {key: "AudioVolumeDown"});
+      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "AudioVolumeDown" });
     });
 
     volumeButtons.appendChild(volumeUp);
     volumeButtons.appendChild(volumeDown);
     this.stack.appendChild(volumeButtons);
 
     let bottomToolbar = this.chromeDoc.createElement("toolbar");
     bottomToolbar.className = "devtools-responsiveui-hardware-buttons";
     bottomToolbar.setAttribute("align", "center");
     bottomToolbar.setAttribute("pack", "center");
 
     let homeButton = this.chromeDoc.createElement("toolbarbutton");
-    homeButton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-home-button";
+    homeButton.className =
+      "devtools-responsiveui-toolbarbutton devtools-responsiveui-home-button";
     homeButton.addEventListener("mousedown", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keydown", {key: "Home"});
+      SystemAppProxy.dispatchKeyboardEvent("keydown", { key: "Home" });
     });
     homeButton.addEventListener("mouseup", () => {
-      SystemAppProxy.dispatchKeyboardEvent("keyup", {key: "Home"});
+      SystemAppProxy.dispatchKeyboardEvent("keyup", { key: "Home" });
     });
     bottomToolbar.appendChild(homeButton);
     this.bottomToolbar = bottomToolbar;
     this.container.appendChild(bottomToolbar);
   },
 
   /**
    * Validate and apply any user input on the editable menulist
    */
-  handleManualInput: function RUI_handleManualInput() {
+  handleManualInput: function () {
     let userInput = this.menulist.inputField.value;
     let value = INPUT_PARSER.exec(userInput);
     let selectedPreset = this.menuitems.get(this.selectedItem);
 
     // In case of an invalide value, we show back the last preset
     if (!value || value.length < 3) {
       this.setMenuLabel(this.selectedItem, selectedPreset);
       return;
@@ -683,19 +713,19 @@ ResponsiveUI.prototype = {
       width: w,
       height: h,
     });
   },
 
   /**
    * Build the presets list and append it to the menupopup.
    *
-   * @param aParent menupopup.
+   * @param parent menupopup.
    */
-  registerPresets: function RUI_registerPresets(aParent) {
+  registerPresets: function (parent) {
     let fragment = this.chromeDoc.createDocumentFragment();
     let doc = this.chromeDoc;
 
     for (let preset of this.presets) {
       let menuitem = doc.createElement("menuitem");
       menuitem.setAttribute("ispreset", true);
       this.menuitems.set(menuitem, preset);
 
@@ -706,47 +736,49 @@ ResponsiveUI.prototype = {
 
       if (preset.custom) {
         this.customMenuitem = menuitem;
       }
 
       this.setMenuLabel(menuitem, preset);
       fragment.appendChild(menuitem);
     }
-    aParent.appendChild(fragment);
+    parent.appendChild(fragment);
   },
 
   /**
    * Set the menuitem label of a preset.
    *
-   * @param aMenuitem menuitem to edit.
-   * @param aPreset associated preset.
+   * @param menuitem menuitem to edit.
+   * @param preset associated preset.
    */
-  setMenuLabel: function RUI_setMenuLabel(aMenuitem, aPreset) {
+  setMenuLabel: function (menuitem, preset) {
     let size = SHARED_L10N.getFormatStr("dimensions",
-      Math.round(aPreset.width), Math.round(aPreset.height));
+      Math.round(preset.width), Math.round(preset.height));
 
     // .inputField might be not reachable yet (async XBL loading)
     if (this.menulist.inputField) {
       this.menulist.inputField.value = size;
     }
 
-    if (aPreset.custom) {
-      size = this.strings.formatStringFromName("responsiveUI.customResolution", [size], 1);
-    } else if (aPreset.name != null && aPreset.name !== "") {
-      size = this.strings.formatStringFromName("responsiveUI.namedResolution", [size, aPreset.name], 2);
+    if (preset.custom) {
+      size = this.strings.formatStringFromName("responsiveUI.customResolution",
+                                               [size], 1);
+    } else if (preset.name != null && preset.name !== "") {
+      size = this.strings.formatStringFromName("responsiveUI.namedResolution",
+                                               [size, preset.name], 2);
     }
 
-    aMenuitem.setAttribute("label", size);
+    menuitem.setAttribute("label", size);
   },
 
   /**
    * When a preset is selected, apply it.
    */
-  presetSelected: function RUI_presetSelected() {
+  presetSelected: function () {
     if (this.menulist.selectedItem.getAttribute("ispreset") === "true") {
       this.selectedItem = this.menulist.selectedItem;
 
       this.rotateValue = false;
       let selectedPreset = this.menuitems.get(this.selectedItem);
       this.loadPreset(selectedPreset);
       this.currentPresetKey = selectedPreset.key;
       this.saveCurrentPreset();
@@ -767,23 +799,24 @@ ResponsiveUI.prototype = {
    */
   loadPreset(preset) {
     this.setViewportSize(preset);
   },
 
   /**
    * Add a preset to the list and the memory
    */
-  addPreset: function RUI_addPreset() {
+  addPreset: function () {
     let w = this.customPreset.width;
     let h = this.customPreset.height;
     let newName = {};
 
     let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle1");
-    let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2);
+    let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg",
+                                                    [w, h], 2);
     let promptOk = Services.prompt.prompt(null, title, message, newName, null, {});
 
     if (!promptOk) {
       // Prompt has been cancelled
       this.menulist.selectedItem = this.selectedItem;
       return;
     }
 
@@ -792,34 +825,32 @@ ResponsiveUI.prototype = {
       name: newName.value,
       width: w,
       height: h
     };
 
     this.presets.push(newPreset);
 
     // Sort the presets according to width/height ascending order
-    this.presets.sort(function RUI_sortPresets(aPresetA, aPresetB) {
+    this.presets.sort((presetA, presetB) => {
       // We keep custom preset at first
-      if (aPresetA.custom && !aPresetB.custom) {
+      if (presetA.custom && !presetB.custom) {
         return 1;
       }
-      if (!aPresetA.custom && aPresetB.custom) {
+      if (!presetA.custom && presetB.custom) {
         return -1;
       }
 
-      if (aPresetA.width === aPresetB.width) {
-        if (aPresetA.height === aPresetB.height) {
+      if (presetA.width === presetB.width) {
+        if (presetA.height === presetB.height) {
           return 0;
-        } else {
-          return aPresetA.height > aPresetB.height;
         }
-      } else {
-        return aPresetA.width > aPresetB.width;
+        return presetA.height > presetB.height;
       }
+      return presetA.width > presetB.width;
     });
 
     this.savePresets();
 
     let newMenuitem = this.chromeDoc.createElement("menuitem");
     newMenuitem.setAttribute("ispreset", true);
     this.setMenuLabel(newMenuitem, newPreset);
 
@@ -831,17 +862,17 @@ ResponsiveUI.prototype = {
     this.menulist.selectedItem = newMenuitem;
     this.currentPresetKey = newPreset.key;
     this.saveCurrentPreset();
   },
 
   /**
    * remove a preset from the list and the memory
    */
-  removePreset: function RUI_removePreset() {
+  removePreset: function () {
     let selectedPreset = this.menuitems.get(this.selectedItem);
     let w = selectedPreset.width;
     let h = selectedPreset.height;
 
     this.presets.splice(this.presets.indexOf(selectedPreset), 1);
     this.menulist.firstChild.removeChild(this.selectedItem);
     this.menuitems.delete(this.selectedItem);
 
@@ -858,17 +889,17 @@ ResponsiveUI.prototype = {
     });
 
     this.savePresets();
   },
 
   /**
    * Swap width and height.
    */
-  rotate: function RUI_rotate() {
+  rotate: function () {
     let selectedPreset = this.menuitems.get(this.selectedItem);
     let width = this.rotateValue ? selectedPreset.height : selectedPreset.width;
     let height = this.rotateValue ? selectedPreset.width : selectedPreset.height;
 
     this.setViewportSize({
       width: height,
       height: width,
     });
@@ -879,108 +910,114 @@ ResponsiveUI.prototype = {
       this.rotateValue = !this.rotateValue;
       this.saveCurrentPreset();
     }
   },
 
   /**
    * Take a screenshot of the page.
    *
-   * @param aFileName name of the screenshot file (used for tests).
+   * @param filename name of the screenshot file (used for tests).
    */
-  screenshot: function RUI_screenshot(aFileName) {
-    let filename = aFileName;
+  screenshot: function (filename) {
     if (!filename) {
       let date = new Date();
       let month = ("0" + (date.getMonth() + 1)).substr(-2, 2);
       let day = ("0" + date.getDate()).substr(-2, 2);
       let dateString = [date.getFullYear(), month, day].join("-");
       let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
-      filename = this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename", [dateString, timeString], 2);
+      filename =
+        this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename",
+                                          [dateString, timeString], 2);
     }
     let mm = this.tab.linkedBrowser.messageManager;
     let chromeWindow = this.chromeDoc.defaultView;
     let doc = chromeWindow.document;
-    function onScreenshot(aMessage) {
+    function onScreenshot(message) {
       mm.removeMessageListener("ResponsiveMode:RequestScreenshot:Done", onScreenshot);
-      chromeWindow.saveURL(aMessage.data, filename + ".png", null, true, true, doc.documentURIObject, doc);
+      chromeWindow.saveURL(message.data, filename + ".png", null, true, true,
+                           doc.documentURIObject, doc);
     }
     mm.addMessageListener("ResponsiveMode:RequestScreenshot:Done", onScreenshot);
     mm.sendAsyncMessage("ResponsiveMode:RequestScreenshot");
   },
 
   /**
    * Enable/Disable mouse -> touch events translation.
    */
-  enableTouch: function RUI_enableTouch() {
+  enableTouch: function () {
     this.touchbutton.setAttribute("checked", "true");
     return this.touchEventSimulator.start();
   },
 
-  disableTouch: function RUI_disableTouch() {
+  disableTouch: function () {
     this.touchbutton.removeAttribute("checked");
     return this.touchEventSimulator.stop();
   },
 
-  hideTouchNotification: function RUI_hideTouchNotification() {
+  hideTouchNotification: function () {
     let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
     let n = nbox.getNotificationWithValue("responsive-ui-need-reload");
     if (n) {
       n.close();
     }
   },
 
   toggleTouch: Task.async(function* () {
     this.hideTouchNotification();
     if (this.touchEventSimulator.enabled) {
       this.disableTouch();
-    } else {
-      let isReloadNeeded = yield this.enableTouch();
-      if (isReloadNeeded) {
-        if (Services.prefs.getBoolPref("devtools.responsiveUI.no-reload-notification")) {
-          return;
-        }
+      return;
+    }
 
-        let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
+    let isReloadNeeded = yield this.enableTouch();
+    if (!isReloadNeeded) {
+      return;
+    }
+
+    const PREF = "devtools.responsiveUI.no-reload-notification";
+    if (Services.prefs.getBoolPref(PREF)) {
+      return;
+    }
+
+    let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
 
-        var buttons = [{
-          label: this.strings.GetStringFromName("responsiveUI.notificationReload"),
-          callback: () => {
-            this.browser.reload();
-          },
-          accessKey: this.strings.GetStringFromName("responsiveUI.notificationReload_accesskey"),
-        }, {
-          label: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification"),
-          callback: function () {
-            Services.prefs.setBoolPref("devtools.responsiveUI.no-reload-notification", true);
-          },
-          accessKey: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification_accesskey"),
-        }];
+    let buttons = [{
+      label: this.strings.GetStringFromName("responsiveUI.notificationReload"),
+      callback: () => this.browser.reload(),
+      accessKey:
+        this.strings.GetStringFromName("responsiveUI.notificationReload_accesskey"),
+    }, {
+      label: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification"),
+      callback: () => Services.prefs.setBoolPref(PREF, true),
+      accessKey:
+        this.strings
+            .GetStringFromName("responsiveUI.dontShowReloadNotification_accesskey"),
+    }];
 
-        nbox.appendNotification(
-           this.strings.GetStringFromName("responsiveUI.needReload"),
-           "responsive-ui-need-reload",
-           null,
-           nbox.PRIORITY_INFO_LOW,
-           buttons);
-      }
-    }
+    nbox.appendNotification(
+      this.strings.GetStringFromName("responsiveUI.needReload"),
+      "responsive-ui-need-reload",
+      null,
+      nbox.PRIORITY_INFO_LOW,
+      buttons
+    );
   }),
 
   waitForReload() {
-    let navigatedDeferred = promise.defer();
-    let onNavigated = (_, { state }) => {
-      if (state != "stop") {
-        return;
-      }
-      this.client.removeListener("tabNavigated", onNavigated);
-      navigatedDeferred.resolve();
-    };
-    this.client.addListener("tabNavigated", onNavigated);
-    return navigatedDeferred.promise;
+    return new Promise(resolve => {
+      let onNavigated = (_, { state }) => {
+        if (state != "stop") {
+          return;
+        }
+        this.client.removeListener("tabNavigated", onNavigated);
+        resolve();
+      };
+      this.client.addListener("tabNavigated", onNavigated);
+    });
   },
 
   /**
    * Change the user agent string
    */
   changeUA: Task.async(function* () {
     let value = this.userAgentInput.value;
     let changed;
@@ -1019,97 +1056,106 @@ ResponsiveUI.prototype = {
     if (width) {
       this.setWidth(width);
     }
     if (height) {
       this.setHeight(height);
     }
   },
 
-  setWidth: function RUI_setWidth(aWidth) {
-    aWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH);
-    this.stack.style.maxWidth = this.stack.style.minWidth = aWidth + "px";
+  setWidth: function (width) {
+    width = Math.min(Math.max(width, MIN_WIDTH), MAX_WIDTH);
+    this.stack.style.maxWidth = this.stack.style.minWidth = width + "px";
 
-    if (!this.ignoreX)
-      this.resizeBarH.setAttribute("left", Math.round(aWidth / 2));
+    if (!this.ignoreX) {
+      this.resizeBarH.setAttribute("left", Math.round(width / 2));
+    }
 
     let selectedPreset = this.menuitems.get(this.selectedItem);
 
     if (selectedPreset.custom) {
-      selectedPreset.width = aWidth;
+      selectedPreset.width = width;
       this.setMenuLabel(this.selectedItem, selectedPreset);
     }
   },
 
-  setHeight: function RUI_setHeight(aHeight) {
-    aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_HEIGHT);
-    this.stack.style.maxHeight = this.stack.style.minHeight = aHeight + "px";
+  setHeight: function (height) {
+    height = Math.min(Math.max(height, MIN_HEIGHT), MAX_HEIGHT);
+    this.stack.style.maxHeight = this.stack.style.minHeight = height + "px";
 
-    if (!this.ignoreY)
-      this.resizeBarV.setAttribute("top", Math.round(aHeight / 2));
+    if (!this.ignoreY) {
+      this.resizeBarV.setAttribute("top", Math.round(height / 2));
+    }
 
     let selectedPreset = this.menuitems.get(this.selectedItem);
     if (selectedPreset.custom) {
-      selectedPreset.height = aHeight;
+      selectedPreset.height = height;
       this.setMenuLabel(this.selectedItem, selectedPreset);
     }
   },
   /**
    * Start the process of resizing the browser.
    *
-   * @param aEvent
+   * @param event
    */
-  startResizing: function RUI_startResizing(aEvent) {
+  startResizing: function (event) {
     let selectedPreset = this.menuitems.get(this.selectedItem);
 
     if (!selectedPreset.custom) {
-      this.customPreset.width = this.rotateValue ? selectedPreset.height : selectedPreset.width;
-      this.customPreset.height = this.rotateValue ? selectedPreset.width : selectedPreset.height;
+      if (this.rotateValue) {
+        this.customPreset.width = selectedPreset.height;
+        this.customPreset.height = selectedPreset.width;
+      } else {
+        this.customPreset.width = selectedPreset.width;
+        this.customPreset.height = selectedPreset.height;
+      }
 
       let menuitem = this.customMenuitem;
       this.setMenuLabel(menuitem, this.customPreset);
 
       this.currentPresetKey = this.customPreset.key;
       this.menulist.selectedItem = menuitem;
     }
-    this.mainWindow.addEventListener("mouseup", this.bound_stopResizing, true);
-    this.mainWindow.addEventListener("mousemove", this.bound_onDrag, true);
+    this.mainWindow.addEventListener("mouseup", this.boundStopResizing, true);
+    this.mainWindow.addEventListener("mousemove", this.boundOnDrag, true);
     this.container.style.pointerEvents = "none";
 
     this._resizing = true;
     this.stack.setAttribute("notransition", "true");
 
-    this.lastScreenX = aEvent.screenX;
-    this.lastScreenY = aEvent.screenY;
+    this.lastScreenX = event.screenX;
+    this.lastScreenY = event.screenY;
 
-    this.ignoreY = (aEvent.target === this.resizeBarV);
-    this.ignoreX = (aEvent.target === this.resizeBarH);
+    this.ignoreY = (event.target === this.resizeBarV);
+    this.ignoreX = (event.target === this.resizeBarH);
 
     this.isResizing = true;
   },
 
   /**
    * Resizing on mouse move.
    *
-   * @param aEvent
+   * @param event
    */
-  onDrag: function RUI_onDrag(aEvent) {
-    let shift = aEvent.shiftKey;
-    let ctrl = !aEvent.shiftKey && aEvent.ctrlKey;
+  onDrag: function (event) {
+    let shift = event.shiftKey;
+    let ctrl = !event.shiftKey && event.ctrlKey;
 
-    let screenX = aEvent.screenX;
-    let screenY = aEvent.screenY;
+    let screenX = event.screenX;
+    let screenY = event.screenY;
 
     let deltaX = screenX - this.lastScreenX;
     let deltaY = screenY - this.lastScreenY;
 
-    if (this.ignoreY)
+    if (this.ignoreY) {
       deltaY = 0;
-    if (this.ignoreX)
+    }
+    if (this.ignoreX) {
       deltaX = 0;
+    }
 
     if (ctrl) {
       deltaX /= SLOW_RATIO;
       deltaY /= SLOW_RATIO;
     }
 
     let width = this.customPreset.width + deltaX;
     let height = this.customPreset.height + deltaY;
@@ -1137,57 +1183,61 @@ ResponsiveUI.prototype = {
     }
 
     this.setViewportSize({ width, height });
   },
 
   /**
    * Stop End resizing
    */
-  stopResizing: function RUI_stopResizing() {
+  stopResizing: function () {
     this.container.style.pointerEvents = "auto";
 
-    this.mainWindow.removeEventListener("mouseup", this.bound_stopResizing, true);
-    this.mainWindow.removeEventListener("mousemove", this.bound_onDrag, true);
+    this.mainWindow.removeEventListener("mouseup", this.boundStopResizing, true);
+    this.mainWindow.removeEventListener("mousemove", this.boundOnDrag, true);
 
     this.saveCustomSize();
 
     delete this._resizing;
     if (this.transitionsEnabled) {
       this.stack.removeAttribute("notransition");
     }
     this.ignoreY = false;
     this.ignoreX = false;
     this.isResizing = false;
   },
 
   /**
    * Store the custom size as a pref.
    */
-  saveCustomSize: function RUI_saveCustomSize() {
-    Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.customPreset.width);
-    Services.prefs.setIntPref("devtools.responsiveUI.customHeight", this.customPreset.height);
+  saveCustomSize: function () {
+    Services.prefs.setIntPref("devtools.responsiveUI.customWidth",
+                              this.customPreset.width);
+    Services.prefs.setIntPref("devtools.responsiveUI.customHeight",
+                              this.customPreset.height);
   },
 
   /**
    * Store the current preset as a pref.
    */
-  saveCurrentPreset: function RUI_saveCurrentPreset() {
-    Services.prefs.setCharPref("devtools.responsiveUI.currentPreset", this.currentPresetKey);
-    Services.prefs.setBoolPref("devtools.responsiveUI.rotate", this.rotateValue);
+  saveCurrentPreset: function () {
+    Services.prefs.setCharPref("devtools.responsiveUI.currentPreset",
+                               this.currentPresetKey);
+    Services.prefs.setBoolPref("devtools.responsiveUI.rotate",
+                               this.rotateValue);
   },
 
   /**
    * Store the list of all registered presets as a pref.
    */
-  savePresets: function RUI_savePresets() {
+  savePresets: function () {
     // We exclude the custom one
-    let registeredPresets = this.presets.filter(function (aPreset) {
-      return !aPreset.custom;
+    let registeredPresets = this.presets.filter(function (preset) {
+      return !preset.custom;
     });
-
-    Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets));
+    Services.prefs.setCharPref("devtools.responsiveUI.presets",
+                               JSON.stringify(registeredPresets));
   },
 };
 
 loader.lazyGetter(ResponsiveUI.prototype, "strings", function () {
   return Services.strings.createBundle("chrome://devtools/locale/responsiveUI.properties");
 });
--- a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
+++ b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
@@ -39,18 +39,25 @@ function* testButton(toolbox, Telemetry)
 
 function* delayedClicks(toolbox, node, clicks) {
   for (let i = 0; i < clicks; i++) {
     yield new Promise(resolve => {
       // See TOOL_DELAY for why we need setTimeout here
       setTimeout(() => resolve(), TOOL_DELAY);
     });
 
-    let PaintFlashingCmd = require("devtools/shared/gcli/commands/paintflashing");
-    let clicked = PaintFlashingCmd.eventEmitter.once("changed");
+    let { CommandState } = require("devtools/shared/gcli/command-state");
+    let clicked = new Promise(resolve => {
+      CommandState.on("changed", function changed(type, { command }) {
+        if (command === "paintflashing") {
+          CommandState.off("changed", changed);
+          resolve();
+        }
+      });
+    });
 
     info("Clicking button " + node.id);
     node.click();
 
     yield clicked;
   }
 }
 
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -476,16 +476,60 @@ const TEST_DATA = [
 
   {
     desc: "regression test for bug 1328016",
     input: "position:absolute;top50px;height:50px;width:50px;",
     instruction: {type: "set", name: "width", value: "60px", priority: "",
                   index: 2},
     expected: "position:absolute;top50px;height:50px;width:60px;",
   },
+
+  {
+    desc: "url regression test for bug 1321970",
+    input: "",
+    instruction: {type: "create", name: "p", value: "url(", priority: "",
+                  index: 0, enabled: true},
+    expected: "p: url();",
+    changed: {0: "url()"}
+  },
+
+  {
+    desc: "url semicolon regression test for bug 1321970",
+    input: "",
+    instruction: {type: "create", name: "p", value: "url(;", priority: "",
+                  index: 0, enabled: true},
+    expected: "p: url();",
+    changed: {0: "url()"}
+  },
+
+  {
+    desc: "basic regression test for bug 1321970",
+    input: "",
+    instruction: {type: "create", name: "p", value: "(", priority: "",
+                  index: 0, enabled: true},
+    expected: "p: \\(;",
+    changed: {0: "\\("}
+  },
+
+  {
+    desc: "unbalanced regression test for bug 1321970",
+    input: "",
+    instruction: {type: "create", name: "p", value: "({[})", priority: "",
+                  index: 0, enabled: true},
+    expected: "p: ({\\[});",
+    changed: {0: "({\\[})"}
+  },
+
+  {
+    desc: "function regression test for bug 1321970",
+    input: "",
+    instruction: {type: "create", name: "p", value: "func(1,2)", priority: "",
+                  index: 0, enabled: true},
+    expected: "p: func(1,2);",
+  },
 ];
 
 function rewriteDeclarations(inputString, instruction, defaultIndentation) {
   let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
   rewriter.defaultIndentation = defaultIndentation;
 
   switch (instruction.type) {
     case "rename":
--- a/devtools/client/shared/widgets/FlameGraph.js
+++ b/devtools/client/shared/widgets/FlameGraph.js
@@ -25,59 +25,50 @@ loader.lazyRequireGetter(this, "Abstract
   "devtools/client/shared/widgets/Graphs", true);
 loader.lazyRequireGetter(this, "GraphArea",
   "devtools/client/shared/widgets/Graphs", true);
 loader.lazyRequireGetter(this, "GraphAreaDragger",
   "devtools/client/shared/widgets/Graphs", true);
 
 const GRAPH_SRC = "chrome://devtools/content/shared/widgets/graphs-frame.xhtml";
 
-// ms
-const GRAPH_RESIZE_EVENTS_DRAIN = 100;
+const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
 
 const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00035;
 const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.5;
 const GRAPH_KEYBOARD_ZOOM_SENSITIVITY = 20;
 const GRAPH_KEYBOARD_PAN_SENSITIVITY = 20;
 const GRAPH_KEYBOARD_ACCELERATION = 1.05;
 const GRAPH_KEYBOARD_TRANSLATION_MAX = 150;
 
-// ms
-const GRAPH_MIN_SELECTION_WIDTH = 0.001;
+const GRAPH_MIN_SELECTION_WIDTH = 0.001; // ms
 
-// px
-const GRAPH_HORIZONTAL_PAN_THRESHOLD = 10;
-const GRAPH_VERTICAL_PAN_THRESHOLD = 30;
+const GRAPH_HORIZONTAL_PAN_THRESHOLD = 10; // px
+const GRAPH_VERTICAL_PAN_THRESHOLD = 30; // px
 
 const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
 
-// ms
-const TIMELINE_TICKS_MULTIPLE = 5;
-// px
-const TIMELINE_TICKS_SPACING_MIN = 75;
+const TIMELINE_TICKS_MULTIPLE = 5; // ms
+const TIMELINE_TICKS_SPACING_MIN = 75; // px
 
-// px
-const OVERVIEW_HEADER_HEIGHT = 16;
-const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9;
+const OVERVIEW_HEADER_HEIGHT = 16; // px
+const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px
 const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif";
-// px
-const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6;
-const OVERVIEW_HEADER_TEXT_PADDING_TOP = 5;
+const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px
+const OVERVIEW_HEADER_TEXT_PADDING_TOP = 5; // px
 const OVERVIEW_HEADER_TIMELINE_STROKE_COLOR = "rgba(128, 128, 128, 0.5)";
 
-// px
-const FLAME_GRAPH_BLOCK_HEIGHT = 15;
-const FLAME_GRAPH_BLOCK_BORDER = 1;
-const FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE = 10;
+const FLAME_GRAPH_BLOCK_HEIGHT = 15; // px
+const FLAME_GRAPH_BLOCK_BORDER = 1; // px
+const FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE = 10; // px
 const FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY = "message-box, Helvetica Neue," +
                                            "Helvetica, sans-serif";
-// px
-const FLAME_GRAPH_BLOCK_TEXT_PADDING_TOP = 0;
-const FLAME_GRAPH_BLOCK_TEXT_PADDING_LEFT = 3;
-const FLAME_GRAPH_BLOCK_TEXT_PADDING_RIGHT = 3;
+const FLAME_GRAPH_BLOCK_TEXT_PADDING_TOP = 0; // px
+const FLAME_GRAPH_BLOCK_TEXT_PADDING_LEFT = 3; // px
+const FLAME_GRAPH_BLOCK_TEXT_PADDING_RIGHT = 3; // px
 
 // Large enough number for a diverse pallette.
 const PALLETTE_SIZE = 20;
 const PALLETTE_HUE_OFFSET = Math.random() * 90;
 const PALLETTE_HUE_RANGE = 270;
 const PALLETTE_SATURATION = 100;
 const PALLETTE_BRIGHTNESS = 55;
 const PALLETTE_OPACITY = 0.35;
--- a/devtools/client/shared/widgets/Graphs.js
+++ b/devtools/client/shared/widgets/Graphs.js
@@ -16,38 +16,33 @@ loader.lazyImporter(this, "DevToolsWorke
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://devtools/content/shared/widgets/graphs-frame.xhtml";
 const WORKER_URL =
   "resource://devtools/client/shared/widgets/GraphsWorker.js";
 
 // Generic constants.
 
-// ms
-const GRAPH_RESIZE_EVENTS_DRAIN = 100;
+const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
 const GRAPH_WHEEL_ZOOM_SENSITIVITY = 0.00075;
 const GRAPH_WHEEL_SCROLL_SENSITIVITY = 0.1;
-// px
-const GRAPH_WHEEL_MIN_SELECTION_WIDTH = 10;
+const GRAPH_WHEEL_MIN_SELECTION_WIDTH = 10; // px
 
-// px
-const GRAPH_SELECTION_BOUNDARY_HOVER_LINE_WIDTH = 4;
-const GRAPH_SELECTION_BOUNDARY_HOVER_THRESHOLD = 10;
-const GRAPH_MAX_SELECTION_LEFT_PADDING = 1;
-const GRAPH_MAX_SELECTION_RIGHT_PADDING = 1;
+const GRAPH_SELECTION_BOUNDARY_HOVER_LINE_WIDTH = 4; // px
+const GRAPH_SELECTION_BOUNDARY_HOVER_THRESHOLD = 10; // px
+const GRAPH_MAX_SELECTION_LEFT_PADDING = 1; // px
+const GRAPH_MAX_SELECTION_RIGHT_PADDING = 1; // px
 
-// px
-const GRAPH_REGION_LINE_WIDTH = 1;
+const GRAPH_REGION_LINE_WIDTH = 1; // px
 const GRAPH_REGION_LINE_COLOR = "rgba(237,38,85,0.8)";
 
-// px
-const GRAPH_STRIPE_PATTERN_WIDTH = 16;
-const GRAPH_STRIPE_PATTERN_HEIGHT = 16;
-const GRAPH_STRIPE_PATTERN_LINE_WIDTH = 2;
-const GRAPH_STRIPE_PATTERN_LINE_SPACING = 4;
+const GRAPH_STRIPE_PATTERN_WIDTH = 16; // px
+const GRAPH_STRIPE_PATTERN_HEIGHT = 16; // px
+const GRAPH_STRIPE_PATTERN_LINE_WIDTH = 2; // px
+const GRAPH_STRIPE_PATTERN_LINE_SPACING = 4; // px
 
 /**
  * Small data primitives for all graphs.
  */
 this.GraphCursor = function () {
   this.x = null;
   this.y = null;
 };
--- a/devtools/client/shared/widgets/LineGraphWidget.js
+++ b/devtools/client/shared/widgets/LineGraphWidget.js
@@ -6,27 +6,24 @@ const { AbstractCanvasGraph, CanvasGraph
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const L10N = new LocalizationHelper("devtools/client/locales/graphs.properties");
 
 // Line graph constants.
 
 const GRAPH_DAMPEN_VALUES_FACTOR = 0.85;
-// px
-const GRAPH_TOOLTIP_SAFE_BOUNDS = 8;
-const GRAPH_MIN_MAX_TOOLTIP_DISTANCE = 14;
+const GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
+const GRAPH_MIN_MAX_TOOLTIP_DISTANCE = 14; // px
 
 const GRAPH_BACKGROUND_COLOR = "#0088cc";
-// px
-const GRAPH_STROKE_WIDTH = 1;
+const GRAPH_STROKE_WIDTH = 1; // px
 const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
-// px
-const GRAPH_HELPER_LINES_DASH = [5];
-const GRAPH_HELPER_LINES_WIDTH = 1;
+const GRAPH_HELPER_LINES_DASH = [5]; // px
+const GRAPH_HELPER_LINES_WIDTH = 1; // px
 const GRAPH_MAXIMUM_LINE_COLOR = "rgba(255,255,255,0.4)";
 const GRAPH_AVERAGE_LINE_COLOR = "rgba(255,255,255,0.7)";
 const GRAPH_MINIMUM_LINE_COLOR = "rgba(255,255,255,0.9)";
 const GRAPH_BACKGROUND_GRADIENT_START = "rgba(255,255,255,0.25)";
 const GRAPH_BACKGROUND_GRADIENT_END = "rgba(255,255,255,0.0)";
 
 const GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_LINE_COLOR = "#fff";
--- a/devtools/client/shared/widgets/MountainGraphWidget.js
+++ b/devtools/client/shared/widgets/MountainGraphWidget.js
@@ -3,22 +3,20 @@
 const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
 const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
 
 // Bar graph constants.
 
 const GRAPH_DAMPEN_VALUES_FACTOR = 0.9;
 
 const GRAPH_BACKGROUND_COLOR = "#ddd";
-// px
-const GRAPH_STROKE_WIDTH = 1;
+const GRAPH_STROKE_WIDTH = 1; // px
 const GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
-// px
-const GRAPH_HELPER_LINES_DASH = [5];
-const GRAPH_HELPER_LINES_WIDTH = 1;
+const GRAPH_HELPER_LINES_DASH = [5]; // px
+const GRAPH_HELPER_LINES_WIDTH = 1; // px
 
 const GRAPH_CLIPHEAD_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_LINE_COLOR = "#fff";
 const GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(44,187,15,0.25)";
 const GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
 const GRAPH_REGION_BACKGROUND_COLOR = "transparent";
 const GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
 
--- a/devtools/client/sourceeditor/css-autocompleter.js
+++ b/devtools/client/sourceeditor/css-autocompleter.js
@@ -48,17 +48,16 @@ const {getClientCssProperties} = require
  * "media" and "keyframes" respectively, although "media" can have suggestions
  * like "max-width", "orientation" etc. Similarly "value" state can also have
  * much better logical suggestions if we fine grain identify a sub state just
  * like we do for the "selector" state.
  */
 
 // Autocompletion types.
 
-/* eslint-disable no-inline-comments */
 const CSS_STATES = {
   "null": "null",
   property: "property",    // foo { bar|: … }
   value: "value",          // foo {bar: baz|}
   selector: "selector",    // f| {bar: baz}
   media: "media",          // @med| , or , @media scr| { }
   keyframes: "keyframes",  // @keyf|
   frame: "frame",          // @keyframs foobar { t|
@@ -68,17 +67,16 @@ const SELECTOR_STATES = {
   "null": "null",
   id: "id",                // #f|
   class: "class",          // #foo.b|
   tag: "tag",              // fo|
   pseudo: "pseudo",        // foo:|
   attribute: "attribute",  // foo[b|
   value: "value",          // foo[bar=b|
 };
-/* eslint-enable no-inline-comments */
 
 /**
  * Constructor for the autocompletion object.
  *
  * @param options {Object} An options object containing the following options:
  *        - walker {Object} The object used for query selecting from the current
  *                 target's DOM.
  *        - maxEntries {Number} Maximum selectors suggestions to display.
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -59,29 +59,27 @@ const COMPAT = {
 
   // The preference keys to use for each category/severity combination, indexed
   // first by category (rows) and then by severity (columns).
   //
   // Most of these rather idiosyncratic names are historical and predate the
   // division of message type into "category" and "severity".
   /* eslint-disable no-multi-spaces */
   /* eslint-disable max-len */
-  /* eslint-disable no-inline-comments */
   PREFERENCE_KEYS: [
     // Error         Warning       Info          Log
     [ "network",     "netwarn",    null,         "networkinfo", ],  // Network
     [ "csserror",    "cssparser",  null,         null,          ],  // CSS
     [ "exception",   "jswarn",     null,         "jslog",       ],  // JS
     [ "error",       "warn",       "info",       "log",         ],  // Web Developer
     [ null,          null,         null,         null,          ],  // Input
     [ null,          null,         null,         null,          ],  // Output
     [ "secerror",    "secwarn",    null,         null,          ],  // Security
     [ "servererror", "serverwarn", "serverinfo", "serverlog",   ],  // Server Logging
   ],
-  /* eslint-enable no-inline-comments */
   /* eslint-enable max-len */
   /* eslint-enable no-multi-spaces */
 
   // The fragment of a CSS class name that identifies each category.
   CATEGORY_CLASS_FRAGMENTS: [ "network", "cssparser", "exception", "console",
                               "input", "output", "security", "server" ],
 
   // The fragment of a CSS class name that identifies each severity.
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -42,20 +42,19 @@ const GRID_LINES_PROPERTIES = {
     alpha: 0.75,
   },
   "implicit": {
     lineDash: [2, 2],
     alpha: 0.5,
   }
 };
 
-// px
-const GRID_GAP_PATTERN_WIDTH = 14;
-const GRID_GAP_PATTERN_HEIGHT = 14;
-const GRID_GAP_PATTERN_LINE_DASH = [5, 3];
+const GRID_GAP_PATTERN_WIDTH = 14; // px
+const GRID_GAP_PATTERN_HEIGHT = 14; // px
+const GRID_GAP_PATTERN_LINE_DASH = [5, 3]; // px
 const GRID_GAP_ALPHA = 0.5;
 
 /**
  * Cached used by `CssGridHighlighter.getGridGapPattern`.
  */
 const gCachedGridPattern = new Map();
 
 // That's the maximum size we can allocate for the canvas, in bytes. See:
@@ -838,26 +837,29 @@ CssGridHighlighter.prototype = extend(Au
    * @param  {Number} endPos
    *         The end position of the cross side of the grid line.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
    * @param  {String} lineType
    *         The grid line type - "edge", "explicit", or "implicit".
    */
   renderLine(linePos, startPos, endPos, dimensionType, lineType) {
-    let ratio = this.win.devicePixelRatio;
+    let { devicePixelRatio } = this.win;
+    let lineWidth = getDisplayPixelRatio(this.win);
+    let offset = (lineWidth / 2) % 1;
 
-    linePos = Math.round(linePos * ratio);
-    startPos = Math.round(startPos * ratio);
-    endPos = Math.round(endPos * ratio);
+    linePos = Math.round(linePos * devicePixelRatio);
+    startPos = Math.round(startPos * devicePixelRatio);
+    endPos = Math.round(endPos * devicePixelRatio);
 
     this.ctx.save();
     this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
     this.ctx.beginPath();
-    this.ctx.translate(.5, .5);
+    this.ctx.translate(offset, offset);
+    this.ctx.lineWidth = lineWidth;
 
     if (dimensionType === COLUMNS) {
       this.ctx.moveTo(linePos, startPos);
       this.ctx.lineTo(linePos, endPos);
     } else {
       this.ctx.moveTo(startPos, linePos);
       this.ctx.lineTo(endPos, linePos);
     }
@@ -878,17 +880,17 @@ CssGridHighlighter.prototype = extend(Au
    *         The line position along the x-axis for a column grid line and
    *         y-axis for a row grid line.
    * @param  {Number} startPos
    *         The start position of the cross side of the grid line.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
    */
   renderGridLineNumber(lineNumber, linePos, startPos, dimensionType) {
-    let devicePixelRatio = this.win.devicePixelRatio;
+    let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
 
     linePos = Math.round(linePos * devicePixelRatio);
     startPos = Math.round(startPos * devicePixelRatio);
 
     this.ctx.save();
 
     let fontSize = (GRID_FONT_SIZE * displayPixelRatio);
@@ -918,25 +920,25 @@ CssGridHighlighter.prototype = extend(Au
    * @param  {Number} endPos
    *         The end position of the cross side of the grid line.
    * @param  {Number} breadth
    *         The grid line breadth value.
    * @param  {String} dimensionType
    *         The grid dimension type which is either the constant COLUMNS or ROWS.
    */
   renderGridGap(linePos, startPos, endPos, breadth, dimensionType) {
-    let ratio = this.win.devicePixelRatio;
+    let { devicePixelRatio } = this.win;
 
-    linePos = Math.round(linePos * ratio);
-    startPos = Math.round(startPos * ratio);
-    endPos = Math.round(endPos * ratio);
-    breadth = Math.round(breadth * ratio);
+    linePos = Math.round(linePos * devicePixelRatio);
+    startPos = Math.round(startPos * devicePixelRatio);
+    endPos = Math.round(endPos * devicePixelRatio);
+    breadth = Math.round(breadth * devicePixelRatio);
 
     this.ctx.save();
-    this.ctx.fillStyle = this.getGridGapPattern(ratio, dimensionType);
+    this.ctx.fillStyle = this.getGridGapPattern(devicePixelRatio, dimensionType);
 
     if (dimensionType === COLUMNS) {
       this.ctx.fillRect(linePos, startPos, breadth, endPos - startPos);
     } else {
       this.ctx.fillRect(startPos, linePos, endPos - startPos, breadth);
     }
     this.ctx.restore();
   },
--- a/devtools/server/actors/utils/map-uri-to-addon-id.js
+++ b/devtools/server/actors/utils/map-uri-to-addon-id.js
@@ -19,18 +19,17 @@ const GRAPHENE_ID = "{d1bfe7d9-c01e-4237
 /**
  * This is a wrapper around amIAddonPathService.mapURIToAddonID which always returns
  * false on B2G and graphene to avoid loading the add-on manager there and
  * reports any exceptions rather than throwing so that the caller doesn't have
  * to worry about them.
  */
 if (!Services.appinfo
     || Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT
-    /* XPCShell */
-    || Services.appinfo.ID === undefined
+    || Services.appinfo.ID === undefined /* XPCShell */
     || Services.appinfo.ID == B2G_ID
     || Services.appinfo.ID == GRAPHENE_ID
     || !AddonPathService) {
   module.exports = function mapURIToAddonId(uri) {
     return false;
   };
 } else {
   module.exports = function mapURIToAddonId(uri) {
--- a/devtools/shared/css/lexer.js
+++ b/devtools/shared/css/lexer.js
@@ -5,18 +5,17 @@
 // A CSS Lexer.  This file is a bit unusual -- it is a more or less
 // direct translation of layout/style/nsCSSScanner.cpp and
 // layout/style/CSSLexer.cpp into JS.  This implements the
 // CSSLexer.webidl interface, and the intent is to try to keep it in
 // sync with changes to the platform CSS lexer.  Due to this goal,
 // this file violates some naming conventions and consequently locally
 // disables some eslint rules.
 
-/* eslint-disable camelcase, no-inline-comments, mozilla/no-aArgs */
-/* eslint-disable no-else-return */
+/* eslint-disable camelcase, mozilla/no-aArgs, no-else-return */
 
 "use strict";
 
 // White space of any kind.  No value fields are used.  Note that
 // comments do *not* count as white space; comments separate tokens
 // but are not themselves tokens.
 const eCSSToken_Whitespace = "whitespace";     //
 // A comment.
--- a/devtools/shared/css/parsing-utils.js
+++ b/devtools/shared/css/parsing-utils.js
@@ -596,22 +596,55 @@ RuleRewriter.prototype = {
    * @param {String} text The input text.  This should include the trailing ";".
    * @return {Array} An array of the form [anySanitized, text], where
    *                 |anySanitized| is a boolean that indicates
    *                  whether anything substantive has changed; and
    *                  where |text| is the text that has been rewritten
    *                  to be "lexically safe".
    */
   sanitizePropertyValue: function (text) {
+    // Start by stripping any trailing ";".  This is done here to
+    // avoid the case where the user types "url(" (which is turned
+    // into "url(;" by the rule view before coming here), being turned
+    // into "url(;)" by this code -- due to the way "url(...)" is
+    // parsed as a single token.
+    text = text.replace(/;$/, "");
     let lexer = getCSSLexer(text);
 
     let result = "";
     let previousOffset = 0;
-    let braceDepth = 0;
+    let parenStack = [];
     let anySanitized = false;
+
+    // Push a closing paren on the stack.
+    let pushParen = (token, closer) => {
+      result += text.substring(previousOffset, token.startOffset);
+      parenStack.push({closer, offset: result.length});
+      result += text.substring(token.startOffset, token.endOffset);
+      previousOffset = token.endOffset;
+    };
+
+    // Pop a closing paren from the stack.
+    let popSomeParens = (closer) => {
+      while (parenStack.length > 0) {
+        let paren = parenStack.pop();
+
+        if (paren.closer === closer) {
+          return true;
+        }
+
+        // Found a non-matching closing paren, so quote it.  Note that
+        // these are processed in reverse order.
+        result = result.substring(0, paren.offset) + "\\" +
+          result.substring(paren.offset);
+        anySanitized = true;
+      }
+      return false;
+    };
+
     while (true) {
       let token = lexer.nextToken();
       if (!token) {
         break;
       }
 
       if (token.tokenType === "symbol") {
         switch (token.text) {
@@ -619,36 +652,49 @@ RuleRewriter.prototype = {
             // We simply drop the ";" here.  This lets us cope with
             // declarations that don't have a ";" and also other
             // termination.  The caller handles adding the ";" again.
             result += text.substring(previousOffset, token.startOffset);
             previousOffset = token.endOffset;
             break;
 
           case "{":
-            ++braceDepth;
+            pushParen(token, "}");
+            break;
+
+          case "(":
+            pushParen(token, ")");
+            break;
+
+          case "[":
+            pushParen(token, "]");
             break;
 
           case "}":
-            --braceDepth;
-            if (braceDepth < 0) {
-              // Found an unmatched close bracket.
-              braceDepth = 0;
+          case ")":
+          case "]":
+            // Did we find an unmatched close bracket?
+            if (!popSomeParens(token.text)) {
               // Copy out text from |previousOffset|.
               result += text.substring(previousOffset, token.startOffset);
               // Quote the offending symbol.
               result += "\\" + token.text;
               previousOffset = token.endOffset;
               anySanitized = true;
             }
             break;
         }
+      } else if (token.tokenType === "function") {
+        pushParen(token, ")");
       }
     }
 
+    // Fix up any unmatched parens.
+    popSomeParens(null);
+
     // Copy out any remaining text, then any needed terminators.
     result += text.substring(previousOffset, text.length);
     let eofFixup = lexer.performEOFFixup("", true);
     if (eofFixup) {
       anySanitized = true;
       result += eofFixup;
     }
     return [anySanitized, result];
--- a/devtools/shared/event-emitter.js
+++ b/devtools/shared/event-emitter.js
@@ -79,23 +79,26 @@
   }
 
   /**
    * Decorate an object with event emitter functionality.
    *
    * @param Object objectToDecorate
    *        Bind all public methods of EventEmitter to
    *        the objectToDecorate object.
+   * @return Object the object given.
    */
   EventEmitter.decorate = function (objectToDecorate) {
     let emitter = new EventEmitter();
     objectToDecorate.on = emitter.on.bind(emitter);
     objectToDecorate.off = emitter.off.bind(emitter);
     objectToDecorate.once = emitter.once.bind(emitter);
     objectToDecorate.emit = emitter.emit.bind(emitter);
+
+    return objectToDecorate;
   };
 
   EventEmitter.prototype = {
     /**
      * Connect a listener.
      *
      * @param string event
      *        The event name to which we're connecting.
new file mode 100644
--- /dev/null
+++ b/devtools/shared/gcli/command-state.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+const getTargetId = ({tab}) => getBrowserForTab(tab).outerWindowID;
+const enabledCommands = new Map();
+
+/**
+ * The `CommandState` is a singleton that provides utility methods to keep the commands'
+ * state in sync between the toolbox, the toolbar and the content.
+ */
+const CommandState = EventEmitter.decorate({
+  /**
+   * Returns if a command is enabled on a given target.
+   *
+   * @param {Object} target
+   *                  The target object must have a tab's reference.
+   * @param {String} command
+   *                  The command's name used in gcli.
+   * @ returns {Boolean} returns `false` if the command is not enabled for the target
+   *                    given, or if the target given hasn't a tab; `true` otherwise.
+   */
+  isEnabledForTarget(target, command) {
+    if (!target.tab || !enabledCommands.has(command)) {
+      return false;
+    }
+
+    return enabledCommands.get(command).has(getTargetId(target));
+  },
+
+  /**
+   * Enables a command on a given target.
+   * Emits a "changed" event to notify potential observers about the new commands state.
+   *
+   * @param {Object} target
+   *                  The target object must have a tab's reference.
+   * @param {String} command
+   *                  The command's name used in gcli.
+   */
+  enableForTarget(target, command) {
+    if (!target.tab) {
+      return;
+    }
+
+    if (!enabledCommands.has(command)) {
+      enabledCommands.set(command, new Set());
+    }
+
+    enabledCommands.get(command).add(getTargetId(target));
+
+    CommandState.emit("changed", {target, command});
+  },
+
+  /**
+   * Disabled a command on a given target.
+   * Emits a "changed" event to notify potential observers about the new commands state.
+   *
+   * @param {Object} target
+   *                  The target object must have a tab's reference.
+   * @param {String} command
+   *                  The command's name used in gcli.
+   */
+  disableForTarget(target, command) {
+    if (!target.tab || !enabledCommands.has(command)) {
+      return;
+    }
+
+    enabledCommands.get(command).delete(getTargetId(target));
+
+    CommandState.emit("changed", {target, command});
+  },
+});
+exports.CommandState = CommandState;
+
--- a/devtools/shared/gcli/commands/measure.js
+++ b/devtools/shared/gcli/commands/measure.js
@@ -1,94 +1,80 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /* globals getOuterId, getBrowserForTab */
 
 "use strict";
 
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
 const events = require("sdk/event/core");
 
-loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+loader.lazyRequireGetter(this, "CommandState",
+  "devtools/shared/gcli/command-state", true);
 
 const l10n = require("gcli/l10n");
 require("devtools/server/actors/inspector");
 const { MeasuringToolHighlighter, HighlighterEnvironment } =
   require("devtools/server/actors/highlighters");
 
 const highlighters = new WeakMap();
-const visibleHighlighters = new Set();
-
-const isCheckedFor = (tab) =>
-  tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
 
 exports.items = [
   // The client measure command is used to maintain the toolbar button state
   // only and redirects to the server command to actually toggle the measuring
   // tool (see `measure_server` below).
   {
     name: "measure",
     runAt: "client",
     description: l10n.lookup("measureDesc"),
     manual: l10n.lookup("measureManual"),
     buttonId: "command-button-measure",
     buttonClass: "command-button command-button-invertable",
     tooltipText: l10n.lookup("measureTooltip"),
     state: {
-      isChecked: ({_tab}) => isCheckedFor(_tab),
-      onChange: (target, handler) => eventEmitter.on("changed", handler),
-      offChange: (target, handler) => eventEmitter.off("changed", handler)
+      isChecked: (target) => CommandState.isEnabledForTarget(target, "measure"),
+      onChange: (target, handler) => CommandState.on("changed", handler),
+      offChange: (target, handler) => CommandState.off("changed", handler)
     },
     exec: function* (args, context) {
       let { target } = context.environment;
 
       // Pipe the call to the server command.
       let response = yield context.updateExec("measure_server");
-      let { visible, id } = response.data;
+      let isEnabled = response.data;
 
-      if (visible) {
-        visibleHighlighters.add(id);
+      if (isEnabled) {
+        CommandState.enableForTarget(target, "measure");
       } else {
-        visibleHighlighters.delete(id);
+        CommandState.disableForTarget(target, "measure");
       }
 
-      eventEmitter.emit("changed", { target });
-
       // Toggle off the button when the page navigates because the measuring
       // tool is removed automatically by the MeasuringToolHighlighter on the
       // server then.
-      let onNavigate = () => {
-        visibleHighlighters.delete(id);
-        eventEmitter.emit("changed", { target });
-      };
-      target.off("will-navigate", onNavigate);
-      target.once("will-navigate", onNavigate);
+      target.once("will-navigate", () =>
+        CommandState.disableForTarget(target, "measure"));
     }
   },
   // The server measure command is hidden by default, it's just used by the
   // client command.
   {
     name: "measure_server",
     runAt: "server",
     hidden: true,
     returnType: "highlighterVisibility",
     exec: function (args, context) {
       let env = context.environment;
       let { document } = env;
-      let id = getOuterId(env.window);
 
       // Calling the command again after the measuring tool has been shown once,
       // hides it.
       if (highlighters.has(document)) {
         let { highlighter } = highlighters.get(document);
         highlighter.destroy();
-        return {visible: false, id};
+        return false;
       }
 
       // Otherwise, display the measuring tool.
       let environment = new HighlighterEnvironment();
       environment.initFromWindow(env.window);
       let highlighter = new MeasuringToolHighlighter(environment);
 
       // Store the instance of the measuring tool highlighter for this document
@@ -101,12 +87,12 @@ exports.items = [
         if (highlighters.has(document)) {
           let { environment: toDestroy } = highlighters.get(document);
           toDestroy.destroy();
           highlighters.delete(document);
         }
       });
 
       highlighter.show();
-      return {visible: true, id};
+      return true;
     }
   }
 ];
--- a/devtools/shared/gcli/commands/paintflashing.js
+++ b/devtools/shared/gcli/commands/paintflashing.js
@@ -1,59 +1,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
-loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+loader.lazyRequireGetter(this, "CommandState",
+  "devtools/shared/gcli/command-state", true);
 
 var telemetry;
 try {
   const Telemetry = require("devtools/client/shared/telemetry");
   telemetry = new Telemetry();
 } catch (e) {
   // DevTools Telemetry module only available in Firefox
 }
 
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
-
-// exports the event emitter to help test know when this command is toggled
-exports.eventEmitter = eventEmitter;
-
 const gcli = require("gcli/index");
 const l10n = require("gcli/l10n");
 
-const enabledPaintFlashing = new Set();
-
-const isCheckedFor = (tab) =>
-  tab ? enabledPaintFlashing.has(getBrowserForTab(tab).outerWindowID) : false;
-
 /**
  * Fire events and telemetry when paintFlashing happens
  */
-function onPaintFlashingChanged(target, state) {
-  const { flashing, id } = state;
-
+function onPaintFlashingChanged(target, flashing) {
   if (flashing) {
-    enabledPaintFlashing.add(id);
+    CommandState.enableForTarget(target, "paintflashing");
   } else {
-    enabledPaintFlashing.delete(id);
+    CommandState.disableForTarget(target, "paintflashing");
   }
 
-  eventEmitter.emit("changed", { target: target });
-  function fireChange() {
-    eventEmitter.emit("changed", { target: target });
-  }
-
-  target.off("navigate", fireChange);
-  target.once("navigate", fireChange);
+  target.once("will-navigate", () =>
+    CommandState.disableForTarget(target, "paintflashing"));
 
   if (!telemetry) {
     return;
   }
   if (flashing) {
     telemetry.toolOpened("paintflashing");
   } else {
     telemetry.toolClosed("paintflashing");
@@ -160,19 +143,19 @@ exports.items = [
   {
     item: "command",
     runAt: "client",
     name: "paintflashing toggle",
     hidden: true,
     buttonId: "command-button-paintflashing",
     buttonClass: "command-button command-button-invertable",
     state: {
-      isChecked: ({_tab}) => isCheckedFor(_tab),
-      onChange: (_, handler) => eventEmitter.on("changed", handler),
-      offChange: (_, handler) => eventEmitter.off("changed", handler),
+      isChecked: (target) => CommandState.isEnabledForTarget(target, "paintflashing"),
+      onChange: (_, handler) => CommandState.on("changed", handler),
+      offChange: (_, handler) => CommandState.off("changed", handler),
     },
     tooltipText: l10n.lookup("paintflashingTooltip"),
     description: l10n.lookup("paintflashingToggleDesc"),
     manual: l10n.lookup("paintflashingManual"),
     exec: function* (args, context) {
       const output = yield context.updateExec("paintflashing_server --state toggle");
 
       onPaintFlashingChanged(context.environment.target, output.data);
@@ -190,15 +173,13 @@ exports.items = [
           name: "selection",
           data: [ "on", "off", "toggle", "query" ]
         }
       },
     ],
     returnType: "paintFlashingState",
     exec: function (args, context) {
       let { window } = context.environment;
-      let id = getOuterId(window);
-      let flashing = setPaintFlashing(window, args.state);
 
-      return { flashing, id };
+      return setPaintFlashing(window, args.state);
     }
   }
 ];
--- a/devtools/shared/gcli/commands/rulers.js
+++ b/devtools/shared/gcli/commands/rulers.js
@@ -1,92 +1,79 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* globals getBrowserForTab */
 
 "use strict";
 
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
 const events = require("sdk/event/core");
+
 loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+loader.lazyRequireGetter(this, "CommandState",
+  "devtools/shared/gcli/command-state", true);
 
 const l10n = require("gcli/l10n");
 require("devtools/server/actors/inspector");
 const { RulersHighlighter, HighlighterEnvironment } =
   require("devtools/server/actors/highlighters");
 
 const highlighters = new WeakMap();
-const visibleHighlighters = new Set();
-
-const isCheckedFor = (tab) =>
-  tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
 
 exports.items = [
   // The client rulers command is used to maintain the toolbar button state only
   // and redirects to the server command to actually toggle the rulers (see
   // rulers_server below).
   {
     name: "rulers",
     runAt: "client",
     description: l10n.lookup("rulersDesc"),
     manual: l10n.lookup("rulersManual"),
     buttonId: "command-button-rulers",
     buttonClass: "command-button command-button-invertable",
     tooltipText: l10n.lookup("rulersTooltip"),
     state: {
-      isChecked: ({_tab}) => isCheckedFor(_tab),
-      onChange: (target, handler) => eventEmitter.on("changed", handler),
-      offChange: (target, handler) => eventEmitter.off("changed", handler)
+      isChecked: (target) => CommandState.isEnabledForTarget(target, "rulers"),
+      onChange: (target, handler) => CommandState.on("changed", handler),
+      offChange: (target, handler) => CommandState.off("changed", handler)
     },
     exec: function* (args, context) {
       let { target } = context.environment;
 
       // Pipe the call to the server command.
       let response = yield context.updateExec("rulers_server");
-      let { visible, id } = response.data;
+      let isEnabled = response.data;
 
-      if (visible) {
-        visibleHighlighters.add(id);
+      if (isEnabled) {
+        CommandState.enableForTarget(target, "rulers");
       } else {
-        visibleHighlighters.delete(id);
+        CommandState.disableForTarget(target, "rulers");
       }
 
-      eventEmitter.emit("changed", { target });
-
       // Toggle off the button when the page navigates because the rulers are
       // removed automatically by the RulersHighlighter on the server then.
-      let onNavigate = () => {
-        visibleHighlighters.delete(id);
-        eventEmitter.emit("changed", { target });
-      };
-      target.off("will-navigate", onNavigate);
-      target.once("will-navigate", onNavigate);
+      target.once("will-navigate", () => CommandState.disableForTarget(target, "rulers"));
     }
   },
   // The server rulers command is hidden by default, it's just used by the
   // client command.
   {
     name: "rulers_server",
     runAt: "server",
     hidden: true,
     returnType: "highlighterVisibility",
     exec: function (args, context) {
       let env = context.environment;
       let { document } = env;
-      let id = getOuterId(env.window);
 
       // Calling the command again after the rulers have been shown once hides
       // them.
       if (highlighters.has(document)) {
         let { highlighter } = highlighters.get(document);
         highlighter.destroy();
-        return {visible: false, id};
+        return false;
       }
 
       // Otherwise, display the rulers.
       let environment = new HighlighterEnvironment();
       environment.initFromWindow(env.window);
       let highlighter = new RulersHighlighter(environment);
 
       // Store the instance of the rulers highlighter for this document so we
@@ -99,12 +86,12 @@ exports.items = [
         if (highlighters.has(document)) {
           let { environment: toDestroy } = highlighters.get(document);
           toDestroy.destroy();
           highlighters.delete(document);
         }
       });
 
       highlighter.show();
-      return {visible: true, id};
+      return true;
     }
   }
 ];
--- a/devtools/shared/gcli/moz.build
+++ b/devtools/shared/gcli/moz.build
@@ -14,10 +14,11 @@ DIRS += [
     'source/lib/gcli/languages',
     'source/lib/gcli/mozui',
     'source/lib/gcli/types',
     'source/lib/gcli/ui',
     'source/lib/gcli/util',
 ]
 
 DevToolsModules(
+    'command-state.js',
     'templater.js'
 )
--- a/devtools/shared/heapsnapshot/tests/unit/test_getCensusIndividuals_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_getCensusIndividuals_01.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
-// eslint-disable no-inline-comments
 
 // Test basic functionality of `CensusUtils.getCensusIndividuals`.
 
 function run_test() {
   const stack1 = saveStack(1);
   const stack2 = saveStack(1);
   const stack3 = saveStack(1);
 
--- a/devtools/shared/security/socket.js
+++ b/devtools/shared/security/socket.js
@@ -353,17 +353,17 @@ function _isInputAlive(input) {
  * these connections.
  */
 function _storeCertOverride(s, host, port) {
   let cert = s.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
               .SSLStatus.serverCert;
   let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                      Ci.nsICertOverrideService.ERROR_MISMATCH;
   certOverrideService.rememberValidityOverride(host, port, cert, overrideBits,
-                                               true /* temporary */); // eslint-disable-line
+                                               true /* temporary */);
 }
 
 /**
  * Creates a new socket listener for remote connections to the DebuggerServer.
  * This helps contain and organize the parts of the server that may differ or
  * are particular to one given listener mechanism vs. another.
  */
 function SocketListener() {}
--- a/devtools/shared/touch/simulator-core.js
+++ b/devtools/shared/touch/simulator-core.js
@@ -259,24 +259,22 @@ SimulatorCore.prototype = {
       } else if (name == "touchend") {
         // If we have a "fast" tap, don't send a click as both will be turned
         // into a click and that breaks eg. checkboxes.
         if (Date.now() - this.touchstartTime < delay) {
           this.cancelClick = true;
         }
       }
       let unwrapped = XPCNativeWrapper.unwrap(target);
-      /* eslint-disable no-inline-comments */
       unwrapped.sendTouchEvent(name, clone([0]),       // event type, id
                                clone([evt.clientX]),   // x
                                clone([evt.clientY]),   // y
                                clone([1]), clone([1]), // rx, ry
                                clone([0]), clone([0]), // rotation, force
                                1);                     // count
-      /* eslint-enable no-inline-comments */
       return;
     }
     let document = target.ownerDocument;
     let content = this.getContent(target);
     if (!content) {
       return;
     }
 
@@ -341,21 +339,19 @@ SimulatorCore.prototype = {
     utils.getViewportInfo(content.innerWidth, content.innerHeight, {},
                           allowZoom, minZoom, maxZoom, {}, {}, autoSize);
 
     // FIXME: On Safari and Chrome mobile platform, if the css property
     // touch-action set to none or manipulation would also suppress 300ms
     // delay. But Firefox didn't support this property now, we can't get
     // this value from utils.getVisitedDependentComputedStyle() to check
     // if we should suppress 300ms delay.
-    /* eslint-disable no-inline-comments */
     if (!allowZoom.value ||                   // user-scalable = no
         minZoom.value === maxZoom.value ||    // minimum-scale = maximum-scale
         autoSize.value                        // width = device-width
     ) {
-    /* eslint-enable no-inline-comments */
       return 0;
     }
     return 300;
   }
 };
 
 exports.SimulatorCore = SimulatorCore;
--- a/dom/animation/ComputedTimingFunction.h
+++ b/dom/animation/ComputedTimingFunction.h
@@ -10,23 +10,51 @@
 #include "nsSMILKeySpline.h"  // nsSMILKeySpline
 #include "nsStyleStruct.h"    // nsTimingFunction
 
 namespace mozilla {
 
 class ComputedTimingFunction
 {
 public:
+  static ComputedTimingFunction
+  CubicBezier(double x1, double y1, double x2, double y2)
+  {
+    return ComputedTimingFunction(x1, y1, x2, y2);
+  }
+  static ComputedTimingFunction
+  Steps(nsTimingFunction::Type aType, uint32_t aSteps)
+  {
+    MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
+               aType == nsTimingFunction::Type::StepEnd,
+               "The type of timing function should be either step-start or "
+               "step-end");
+    MOZ_ASSERT(aSteps > 0, "The number of steps should be 1 or more");
+    return ComputedTimingFunction(aType, aSteps);
+  }
+  static ComputedTimingFunction
+  Frames(uint32_t aFrames)
+  {
+    MOZ_ASSERT(aFrames > 1, "The number of frames should be 2 or more");
+    return ComputedTimingFunction(nsTimingFunction::Type::Frames, aFrames);
+  }
+
+  ComputedTimingFunction() = default;
+  explicit ComputedTimingFunction(const nsTimingFunction& aFunction)
+  {
+    Init(aFunction);
+  }
+  void Init(const nsTimingFunction& aFunction);
+
   // BeforeFlag is used in step timing function.
   // https://w3c.github.io/web-animations/#before-flag
   enum class BeforeFlag {
     Unset,
     Set
   };
-  void Init(const nsTimingFunction &aFunction);
   double GetValue(double aPortion, BeforeFlag aBeforeFlag) const;
   const nsSMILKeySpline* GetFunction() const
   {
     NS_ASSERTION(HasSpline(), "Type mismatch");
     return &mTimingFunction;
   }
   nsTimingFunction::Type GetType() const { return mType; }
   bool HasSpline() const { return nsTimingFunction::IsSplineType(mType); }
@@ -60,16 +88,23 @@ public:
                            BeforeFlag aBeforeFlag)
   {
     return aFunction ? aFunction->GetValue(aPortion, aBeforeFlag) : aPortion;
   }
   static int32_t Compare(const Maybe<ComputedTimingFunction>& aLhs,
                          const Maybe<ComputedTimingFunction>& aRhs);
 
 private:
-  nsTimingFunction::Type mType;
+  ComputedTimingFunction(double x1, double y1, double x2, double y2)
+    : mType(nsTimingFunction::Type::CubicBezier)
+    , mTimingFunction(x1, y1, x2, y2) { }
+  ComputedTimingFunction(nsTimingFunction::Type aType, uint32_t aStepsOrFrames)
+    : mType(aType)
+    , mStepsOrFrames(aStepsOrFrames) { }
+
+  nsTimingFunction::Type mType = nsTimingFunction::Type::Linear;
   nsSMILKeySpline mTimingFunction;
   uint32_t mStepsOrFrames;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ComputedTimingFunction_h
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -191,30 +191,30 @@ KeyframeEffectReadOnly::SetKeyframes(nsT
                                      nsStyleContext* aStyleContext)
 {
   DoSetKeyframes(Move(aKeyframes), Move(aStyleContext));
 }
 
 void
 KeyframeEffectReadOnly::SetKeyframes(
   nsTArray<Keyframe>&& aKeyframes,
-  const ServoComputedStyleValues& aServoValues)
+  const ServoComputedValuesWithParent& aServoValues)
 {
   DoSetKeyframes(Move(aKeyframes), aServoValues);
 }
 
 template<typename StyleType>
 void
 KeyframeEffectReadOnly::DoSetKeyframes(nsTArray<Keyframe>&& aKeyframes,
                                        StyleType&& aStyle)
 {
   static_assert(IsSame<StyleType, nsStyleContext*>::value ||
-                IsSame<StyleType, const ServoComputedStyleValues&>::value,
+                IsSame<StyleType, const ServoComputedValuesWithParent&>::value,
                 "StyleType should be nsStyleContext* or "
-                "const ServoComputedStyleValues&");
+                "const ServoComputedValuesWithParent&");
 
   if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
     return;
   }
 
   mKeyframes = Move(aKeyframes);
   // Apply distribute spacing irrespective of the spacing mode. We will apply
   // the specified spacing mode when we generate computed animation property
@@ -222,18 +222,18 @@ KeyframeEffectReadOnly::DoSetKeyframes(n
   // and need to be performed whenever the style context changes.
   KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
 
   if (mAnimation && mAnimation->IsRelevant()) {
     nsNodeUtils::AnimationChanged(mAnimation);
   }
 
   // We need to call UpdateProperties() if the StyleType is
-  // 'const ServoComputedStyleValues&' (i.e. not a pointer) or nsStyleContext*
-  // is not nullptr.
+  // 'const ServoComputedValuesWithParent&' (i.e. not a pointer) or
+  // nsStyleContext* is not nullptr.
   if (!IsPointer<StyleType>::value || aStyle) {
     UpdateProperties(aStyle);
     MaybeUpdateFrameForCompositor();
   }
 }
 
 const AnimationProperty*
 KeyframeEffectReadOnly::GetEffectiveAnimationOfProperty(
@@ -306,23 +306,23 @@ KeyframeEffectReadOnly::UpdateProperties
 
   const ServoComputedValues* currentStyle =
     aStyleContext->StyleSource().AsServoComputedValues();
   const ServoComputedValues* parentStyle =
     aStyleContext->GetParent()
       ? aStyleContext->GetParent()->StyleSource().AsServoComputedValues()
       : nullptr;
 
-  const ServoComputedStyleValues servoValues = { currentStyle, parentStyle };
+  const ServoComputedValuesWithParent servoValues = { currentStyle, parentStyle };
   DoUpdateProperties(servoValues);
 }
 
 void
 KeyframeEffectReadOnly::UpdateProperties(
-  const ServoComputedStyleValues& aServoValues)
+  const ServoComputedValuesWithParent& aServoValues)
 {
   DoUpdateProperties(aServoValues);
 }
 
 template<typename StyleType>
 void
 KeyframeEffectReadOnly::DoUpdateProperties(StyleType&& aStyle)
 {
@@ -917,19 +917,19 @@ KeyframeEffectReadOnly::ConstructKeyfram
   return effect.forget();
 }
 
 template<typename StyleType>
 nsTArray<AnimationProperty>
 KeyframeEffectReadOnly::BuildProperties(StyleType&& aStyle)
 {
   static_assert(IsSame<StyleType, nsStyleContext*>::value ||
-                IsSame<StyleType, const ServoComputedStyleValues&>::value,
+                IsSame<StyleType, const ServoComputedValuesWithParent&>::value,
                 "StyleType should be nsStyleContext* or "
-                "const ServoComputedStyleValues&");
+                "const ServoComputedValuesWithParent&");
 
   MOZ_ASSERT(aStyle);
 
   nsTArray<AnimationProperty> result;
   // If mTarget is null, return an empty property array.
   if (!mTarget) {
     return result;
   }
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -141,17 +141,17 @@ struct AnimationProperty
            mSegments == aOther.mSegments;
   }
   bool operator!=(const AnimationProperty& aOther) const
   {
     return !(*this == aOther);
   }
 };
 
-struct ServoComputedStyleValues
+struct ServoComputedValuesWithParent
 {
   const ServoComputedValues* mCurrentStyle;
   const ServoComputedValues* mParentStyle;
   explicit operator bool() const { return true; }
 };
 
 struct ElementPropertyTransition;
 
@@ -213,17 +213,17 @@ public:
   void NotifyAnimationTimingUpdated();
   void RequestRestyle(EffectCompositor::RestyleType aRestyleType);
   void SetAnimation(Animation* aAnimation) override;
   void SetKeyframes(JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
                     ErrorResult& aRv);
   void SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
                     nsStyleContext* aStyleContext);
   void SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
-                    const ServoComputedStyleValues& aServoValues);
+                    const ServoComputedValuesWithParent& aServoValues);
 
   // Returns true if the effect includes |aProperty| regardless of whether the
   // property is overridden by !important rule.
   bool HasAnimationOfProperty(nsCSSPropertyID aProperty) const;
 
   // GetEffectiveAnimationOfProperty returns AnimationProperty corresponding
   // to a given CSS property if the effect includes the property and the
   // property is not overridden by !important rules.
@@ -242,17 +242,17 @@ public:
   {
     return mProperties;
   }
 
   // Update |mProperties| by recalculating from |mKeyframes| using
   // |aStyleContext| to resolve specified values.
   void UpdateProperties(nsStyleContext* aStyleContext);
   // Servo version of the above function.
-  void UpdateProperties(const ServoComputedStyleValues& aServoValues);
+  void UpdateProperties(const ServoComputedValuesWithParent& aServoValues);
 
   // Update various bits of state related to running ComposeStyle().
   // We need to update this outside ComposeStyle() because we should avoid
   // mutating any state in ComposeStyle() since it might be called during
   // parallel traversal.
   void WillComposeStyle();
 
   // Updates |aComposeResult| with the animation values produced by this
@@ -304,17 +304,17 @@ public:
   void SetPerformanceWarning(
     nsCSSPropertyID aProperty,
     const AnimationPerformanceWarning& aWarning);
 
   // Cumulative change hint on each segment for each property.
   // This is used for deciding the animation is paint-only.
   void CalculateCumulativeChangeHint(nsStyleContext* aStyleContext);
   void CalculateCumulativeChangeHint(
-    const ServoComputedStyleValues& aServoValues)
+    const ServoComputedValuesWithParent& aServoValues)
   {
   }
 
   // Returns true if all of animation properties' change hints
   // can ignore painting if the animation is not visible.
   // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h
   // in detail which change hint can be ignored.
   bool CanIgnoreIfNotVisible() const;
@@ -406,17 +406,17 @@ protected:
   // Returns underlying style animation value for |aProperty|.
   StyleAnimationValue GetUnderlyingStyle(
     nsCSSPropertyID aProperty,
     const RefPtr<AnimValuesStyleRule>& aAnimationRule);
 
   // Ensure the base styles is available for any properties in |aProperties|.
   void EnsureBaseStyles(nsStyleContext* aStyleContext,
                         const nsTArray<AnimationProperty>& aProperties);
-  void EnsureBaseStyles(const ServoComputedStyleValues& aServoValues,
+  void EnsureBaseStyles(const ServoComputedValuesWithParent& aServoValues,
                         const nsTArray<AnimationProperty>& aProperties)
   {
     // FIXME: Bug 1311257: Support missing keyframes.
   }
 
   // Returns the base style resolved by |aStyleContext| for |aProperty|.
   StyleAnimationValue ResolveBaseStyle(nsCSSPropertyID aProperty,
                                        nsStyleContext* aStyleContext);
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -590,17 +590,17 @@ KeyframeUtils::ApplyDistributeSpacing(ns
   ApplySpacing(aKeyframes, SpacingMode::distribute, eCSSProperty_UNKNOWN,
                emptyArray, nullptr);
 }
 
 /* static */ nsTArray<ComputedKeyframeValues>
 KeyframeUtils::GetComputedKeyframeValues(
   const nsTArray<Keyframe>& aKeyframes,
   dom::Element* aElement,
-  const ServoComputedStyleValues& aServoValues)
+  const ServoComputedValuesWithParent& aServoValues)
 {
   MOZ_ASSERT(aElement);
   MOZ_ASSERT(aElement->IsStyledByServo());
 
   nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
   MOZ_ASSERT(presContext);
 
   return presContext->StyleSet()->AsServo()
--- a/dom/animation/KeyframeUtils.h
+++ b/dom/animation/KeyframeUtils.h
@@ -18,17 +18,17 @@ class nsStyleContext;
 struct ServoComputedValues;
 
 namespace mozilla {
 struct AnimationProperty;
 enum class CSSPseudoElementType : uint8_t;
 class ErrorResult;
 struct Keyframe;
 struct PropertyStyleAnimationValuePair;
-struct ServoComputedStyleValues;
+struct ServoComputedValuesWithParent;
 
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 
 namespace mozilla {
@@ -83,17 +83,17 @@ public:
   static nsTArray<ComputedKeyframeValues>
   GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
                             dom::Element* aElement,
                             nsStyleContext* aStyleContext);
 
   static nsTArray<ComputedKeyframeValues>
   GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
                             dom::Element* aElement,
-                            const ServoComputedStyleValues& aServoValues);
+                            const ServoComputedValuesWithParent& aServoValues);
 
   /**
    * Fills in the mComputedOffset member of each keyframe in the given array
    * using the specified spacing mode.
    *
    * https://w3c.github.io/web-animations/#spacing-keyframes
    *
    * @param aKeyframes The set of keyframes to adjust.
@@ -112,17 +112,17 @@ public:
                            SpacingMode aSpacingMode,
                            nsCSSPropertyID aProperty,
                            nsTArray<ComputedKeyframeValues>& aComputedValues,
                            nsStyleContext* aStyleContext);
   static void ApplySpacing(nsTArray<Keyframe>& aKeyframes,
                            SpacingMode aSpacingMode,
                            nsCSSPropertyID aProperty,
                            nsTArray<ComputedKeyframeValues>& aComputedValues,
-                           const ServoComputedStyleValues& aServoValues)
+                           const ServoComputedValuesWithParent& aServoValues)
   {
     NS_WARNING("stylo: ApplySpacing not implemented yet");
   }
 
   /**
    * Wrapper for ApplySpacing to simplify using distribute spacing.
    *
    * @param aKeyframes The set of keyframes to adjust.
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/TimingParams.h"
 
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/dom/AnimatableBinding.h"
 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
+#include "mozilla/ServoBindings.h"
 #include "nsCSSParser.h" // For nsCSSParser
 #include "nsIDocument.h"
 #include "nsRuleNode.h"
 
 namespace mozilla {
 
 template <class OptionsType>
 static const dom::AnimationEffectTimingProperties&
@@ -108,16 +109,36 @@ TimingParams::FromOptionsUnion(
 
 /* static */ Maybe<ComputedTimingFunction>
 TimingParams::ParseEasing(const nsAString& aEasing,
                           nsIDocument* aDocument,
                           ErrorResult& aRv)
 {
   MOZ_ASSERT(aDocument);
 
+  if (aDocument->IsStyledByServo()) {
+    nsTimingFunction timingFunction;
+    nsCString baseString;
+    // FIXME this is using the wrong base uri (bug 1343919)
+    GeckoParserExtraData data(aDocument->GetDocumentURI(),
+                              aDocument->GetDocumentURI(),
+                              aDocument->NodePrincipal());
+    aDocument->GetDocumentURI()->GetSpec(baseString);
+    if (!Servo_ParseEasing(&aEasing, &baseString, &data, &timingFunction)) {
+      aRv.ThrowTypeError<dom::MSG_INVALID_EASING_ERROR>(aEasing);
+      return Nothing();
+    }
+
+    if (timingFunction.mType == nsTimingFunction::Type::Linear) {
+      return Nothing();
+    }
+
+    return Some(ComputedTimingFunction(timingFunction));
+  }
+
   nsCSSValue value;
   nsCSSParser parser;
   parser.ParseLonghandProperty(eCSSProperty_animation_timing_function,
                                aEasing,
                                aDocument->GetDocumentURI(),
                                aDocument->GetDocumentURI(),
                                aDocument->NodePrincipal(),
                                value);
@@ -137,19 +158,17 @@ TimingParams::ParseEasing(const nsAStrin
             return Nothing();
           }
           MOZ_FALLTHROUGH;
         case eCSSUnit_Cubic_Bezier:
         case eCSSUnit_Function:
         case eCSSUnit_Steps: {
           nsTimingFunction timingFunction;
           nsRuleNode::ComputeTimingFunction(list->mValue, timingFunction);
-          ComputedTimingFunction computedTimingFunction;
-          computedTimingFunction.Init(timingFunction);
-          return Some(computedTimingFunction);
+          return Some(ComputedTimingFunction(timingFunction));
         }
         default:
           MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function list "
                                  "item unit");
         break;
       }
       break;
     }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -7927,22 +7927,19 @@ nsDocument::GetEventTargetParent(EventCh
 
   aVisitor.mCanHandle = true;
    // FIXME! This is a hack to make middle mouse paste working also in Editor.
    // Bug 329119
   aVisitor.mForceContentDispatch = true;
 
   // Load events must not propagate to |window| object, see bug 335251.
   if (aVisitor.mEvent->mMessage != eLoad) {
-    nsPIDOMWindowInner* innerWindow = GetInnerWindow();
-    if (innerWindow && innerWindow->IsCurrentInnerWindow()) {
-      nsGlobalWindow* window = nsGlobalWindow::Cast(GetWindow());
-      aVisitor.mParentTarget =
-        window ? window->GetTargetForEventTargetChain() : nullptr;
-    }
+    nsGlobalWindow* window = nsGlobalWindow::Cast(GetWindow());
+    aVisitor.mParentTarget =
+      window ? window->GetTargetForEventTargetChain() : nullptr;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocument::CreateEvent(const nsAString& aEventType, nsIDOMEvent** aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -20,17 +20,17 @@ namespace mozilla {
  * ContentStatesChanged() has to be called when one of them changes thus
  * informing the layout/style engine of the change.
  * Event states are associated with pseudo-classes.
  */
 class EventStates
 {
 public:
   typedef uint64_t InternalType;
-  typedef uint16_t ServoType;
+  typedef uint64_t ServoType;
 
   constexpr EventStates()
     : mStates(0)
   {
   }
 
   // NOTE: the ideal scenario would be to have the default constructor public
   // setting mStates to 0 and this constructor (without = 0) private.
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -518,18 +518,20 @@ HTMLCanvasElement::DispatchPrintCallback
     nsresult rv;
     nsCOMPtr<nsISupports> context;
     rv = GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(context));
     NS_ENSURE_SUCCESS(rv, rv);
   }
   mPrintState = new HTMLCanvasPrintState(this, mCurrentContext, aCallback);
 
   RefPtr<nsRunnableMethod<HTMLCanvasElement> > renderEvent =
-        NewRunnableMethod(this, &HTMLCanvasElement::CallPrintCallback);
-  return NS_DispatchToCurrentThread(renderEvent);
+    NewRunnableMethod(this, &HTMLCanvasElement::CallPrintCallback);
+  return OwnerDoc()->Dispatch("HTMLCanvasElement::CallPrintCallback",
+                              TaskCategory::Other,
+                              renderEvent.forget());
 }
 
 void
 HTMLCanvasElement::CallPrintCallback()
 {
   ErrorResult rv;
   GetMozPrintCallback()->Call(*mPrintState, rv);
 }
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -667,22 +667,20 @@ AsyncPanZoomController::InitializeGlobal
 {
   static bool sInitialized = false;
   if (sInitialized)
     return;
   sInitialized = true;
 
   MOZ_ASSERT(NS_IsMainThread());
 
-  gZoomAnimationFunction = new ComputedTimingFunction();
-  gZoomAnimationFunction->Init(
+  gZoomAnimationFunction = new ComputedTimingFunction(
     nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
   ClearOnShutdown(&gZoomAnimationFunction);
-  gVelocityCurveFunction = new ComputedTimingFunction();
-  gVelocityCurveFunction->Init(
+  gVelocityCurveFunction = new ComputedTimingFunction(
     nsTimingFunction(gfxPrefs::APZCurveFunctionX1(),
                      gfxPrefs::APZCurveFunctionY1(),
                      gfxPrefs::APZCurveFunctionX2(),
                      gfxPrefs::APZCurveFunctionY2()));
   ClearOnShutdown(&gVelocityCurveFunction);
 
   uint64_t sysmem = PR_GetPhysicalMemorySize();
   uint64_t threshold = 1LL << 32; // 4 GB in bytes
--- a/gfx/layers/ipc/LayerAnimationUtils.cpp
+++ b/gfx/layers/ipc/LayerAnimationUtils.cpp
@@ -14,36 +14,30 @@ namespace layers {
 /* static */ Maybe<ComputedTimingFunction>
 AnimationUtils::TimingFunctionToComputedTimingFunction(
   const TimingFunction& aTimingFunction)
 {
   switch (aTimingFunction.type()) {
     case TimingFunction::Tnull_t:
       return Nothing();
     case TimingFunction::TCubicBezierFunction: {
-      ComputedTimingFunction result;
       CubicBezierFunction cbf = aTimingFunction.get_CubicBezierFunction();
-      result.Init(nsTimingFunction(cbf.x1(), cbf.y1(), cbf.x2(), cbf.y2()));
-      return Some(result);
+      return Some(ComputedTimingFunction::CubicBezier(cbf.x1(), cbf.y1(),
+                                                      cbf.x2(), cbf.y2()));
     }
     case TimingFunction::TStepFunction: {
       StepFunction sf = aTimingFunction.get_StepFunction();
       nsTimingFunction::Type type = sf.type() == 1 ?
         nsTimingFunction::Type::StepStart :
         nsTimingFunction::Type::StepEnd;
-      ComputedTimingFunction result;
-      result.Init(nsTimingFunction(type, sf.steps()));
-      return Some(result);
+      return Some(ComputedTimingFunction::Steps(type, sf.steps()));
     }
     case TimingFunction::TFramesFunction: {
       FramesFunction ff = aTimingFunction.get_FramesFunction();
-      ComputedTimingFunction result;
-      result.Init(nsTimingFunction(nsTimingFunction::Type::Frames,
-                                   ff.frames()));
-      return Some(result);
+      return Some(ComputedTimingFunction::Frames(ff.frames()));
     }
     default:
       MOZ_ASSERT_UNREACHABLE(
         "Function must be null, bezier, step or frames");
       break;
   }
   return Nothing();
 }
--- a/gfx/thebes/gfxPattern.cpp
+++ b/gfx/thebes/gfxPattern.cpp
@@ -205,14 +205,8 @@ gfxPattern::GetSolidColor(Color& aColorO
 {
   if (mGfxPattern.GetPattern()->GetType() == PatternType::COLOR) {
     aColorOut = static_cast<ColorPattern*>(mGfxPattern.GetPattern())->mColor;
     return true;
   }
 
  return false;
 }
-
-int
-gfxPattern::CairoStatus()
-{
-  return CAIRO_STATUS_SUCCESS;
-}
--- a/gfx/thebes/gfxPattern.h
+++ b/gfx/thebes/gfxPattern.h
@@ -49,18 +49,16 @@ public:
      */
     mozilla::gfx::Pattern *GetPattern(const mozilla::gfx::DrawTarget *aTarget,
                                       mozilla::gfx::Matrix *aOriginalUserToDevice = nullptr);
     bool IsOpaque();
 
     // clamp, repeat, reflect
     void SetExtend(mozilla::gfx::ExtendMode aExtend);
 
-    int CairoStatus();
-
     void SetSamplingFilter(mozilla::gfx::SamplingFilter aSamplingFilter);
     mozilla::gfx::SamplingFilter SamplingFilter() const;
 
     /* returns TRUE if it succeeded */
     bool GetSolidColor(mozilla::gfx::Color& aColorOut);
 
 private:
     // Private destructor, to discourage deletion outside of Release():
--- a/image/test/reftest/downscaling/reftest-stylo.list
+++ b/image/test/reftest/downscaling/reftest-stylo.list
@@ -101,18 +101,18 @@ default-preferences pref(image.downscale
 == downscale-8px.html?top-to-bottom-16x16-24bpp.bmp downscale-8px.html?top-to-bottom-16x16-24bpp.bmp
 
 # Test downscaling from all supported formats from 256 to 32.
 == downscale-32px.html?.bmp downscale-32px.html?.bmp
 == downscale-32px.html?.gif downscale-32px.html?.gif
 == downscale-32px.html?.jpg downscale-32px.html?.jpg
 == downscale-32px.html?.png downscale-32px.html?.png
 == downscale-32px.html?.svg downscale-32px.html?.svg
-fails fails == downscale-32px.html?-bmp-in.ico downscale-32px.html?.bmp-in.ico
-fails fails == downscale-32px.html?-png-in.ico downscale-32px.html?.png-in.ico
+== downscale-32px.html?-bmp-in.ico downscale-32px.html?-bmp-in.ico
+== downscale-32px.html?-png-in.ico downscale-32px.html?-png-in.ico
 
 # RUN TESTS WITH DOWNSCALE-DURING-DECODE ENABLED:
 # #
 default-preferences pref(image.downscale-during-decode.enabled,true)
 
 == downscale-1.html downscale-1.html
 
 == downscale-2a.html?203,52,left downscale-2a.html?203,52,left
--- a/layout/forms/nsComboboxControlFrame.cpp
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -1462,16 +1462,20 @@ nsComboboxControlFrame::SetInitialChildL
 
 //----------------------------------------------------------------------
   //nsIRollupListener
 //----------------------------------------------------------------------
 bool
 nsComboboxControlFrame::Rollup(uint32_t aCount, bool aFlush,
                                const nsIntPoint* pos, nsIContent** aLastRolledUp)
 {
+  if (aLastRolledUp) {
+    *aLastRolledUp = nullptr;
+  }
+
   if (!mDroppedDown) {
     return false;
   }
 
   bool consume = !!COMBOBOX_ROLLUP_CONSUME_EVENT;
   AutoWeakFrame weakFrame(this);
   mListControlFrame->AboutToRollup(); // might destroy us
   if (!weakFrame.IsAlive()) {
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -3219,19 +3219,16 @@ nsCSSRendering::PaintGradient(nsPresCont
   }
   // Use a pattern transform to take account of source and dest rects
   matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
                             aPresContext->CSSPixelsToDevPixels(aSrc.y)));
   matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
                gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
   gradientPattern->SetMatrix(matrix);
 
-  if (gradientPattern->CairoStatus())
-    return;
-
   if (stopDelta == 0.0) {
     // Non-repeating gradient with all stops in same place -> just add
     // first stop and last stop, both at position 0.
     // Repeating gradient with all stops in the same place, or radial
     // gradient with radius of 0 -> just paint the last stop color.
     // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
     Color firstColor(stops[0].mColor);
     Color lastColor(stops.LastElement().mColor);
--- a/layout/reftests/pagination/reftest-stylo.list
+++ b/layout/reftests/pagination/reftest-stylo.list
@@ -41,17 +41,17 @@ pref(layout.float-fragments-inside-colum
 == float-clear-000-print.html float-clear-000-print.html
 == float-clear-001-print.html float-clear-001-print.html
 == float-clear-002-print.html float-clear-002-print.html
 == float-clear-003-print.html float-clear-003-print.html
 == float-continuations-000.html float-continuations-000.html
 == resize-reflow-000.html resize-reflow-000.html
 == resize-reflow-001.html resize-reflow-001.html
 == table-page-break-before-auto-1.html table-page-break-before-auto-1.html
-fails == table-page-break-before-auto-2.html table-page-break-before-auto-2-ref.html
+== table-page-break-before-auto-2.html table-page-break-before-auto-2.html
 == table-page-break-before-always-1.html table-page-break-before-always-1.html
 == table-page-break-before-left-1.html table-page-break-before-left-1.html
 == table-page-break-before-right-1.html table-page-break-before-right-1.html
 == table-page-break-after-always-1.html table-page-break-after-always-1.html
 == table-page-break-after-left-1.html table-page-break-after-left-1.html
 == table-page-break-after-right-1.html table-page-break-after-right-1.html
 == rowgroup-page-break-after-always-1.html rowgroup-page-break-after-always-1.html
 == row-page-break-after-always-1.html row-page-break-after-always-1.html
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -63,17 +63,17 @@ SERVO_BINDING_FUNC(Servo_StyleSet_Insert
                    RawServoStyleSetBorrowed set, RawServoStyleSheetBorrowed sheet,
                    RawServoStyleSheetBorrowed reference, bool flush)
 SERVO_BINDING_FUNC(Servo_StyleSet_FlushStyleSheets, void, RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_NoteStyleSheetsChanged, void,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_FillKeyframesForName, bool,
                    RawServoStyleSetBorrowed set,
                    const nsACString* property,
-                   const nsTimingFunction* timing_function,
+                   nsTimingFunctionBorrowed timing_function,
                    ServoComputedValuesBorrowed computed_values,
                    RawGeckoKeyframeListBorrowedMut keyframe_list)
 
 // CSSRuleList
 SERVO_BINDING_FUNC(Servo_CssRules_ListTypes, void,
                    ServoCssRulesBorrowed rules,
                    nsTArrayBorrowed_uintptr_t result)
 SERVO_BINDING_FUNC(Servo_CssRules_InsertRule, nsresult,
@@ -113,16 +113,21 @@ SERVO_BINDING_FUNC(Servo_NamespaceRule_G
                    RawServoNamespaceRuleBorrowed rule)
 
 // Animations API
 SERVO_BINDING_FUNC(Servo_ParseProperty,
                    RawServoDeclarationBlockStrong,
                    const nsACString* property, const nsACString* value,
                    const nsACString* base,
                    const GeckoParserExtraData* data)
+SERVO_BINDING_FUNC(Servo_ParseEasing, bool,
+                   const nsAString* easing,
+                   const nsACString* base,
+                   const GeckoParserExtraData* data,
+                   nsTimingFunctionBorrowedMut output)
 SERVO_BINDING_FUNC(Servo_GetComputedKeyframeValues, void,
                    RawGeckoKeyframeListBorrowed keyframes,
                    ServoComputedValuesBorrowed style,
                    ServoComputedValuesBorrowedOrNull parent_style,
                    RawServoStyleSetBorrowed set,
                    RawGeckoComputedKeyframeValuesListBorrowedMut result)
 SERVO_BINDING_FUNC(Servo_AnimationValueMap_Push, void,
                    RawServoAnimationValueMapBorrowed,
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -30,16 +30,17 @@ struct Keyframe;
 struct PropertyStyleAnimationValuePair;
 using ComputedKeyframeValues = nsTArray<PropertyStyleAnimationValuePair>;
 } // namespace mozilla
 
 class nsCSSValue;
 class nsIDocument;
 class nsINode;
 class nsPresContext;
+struct nsTimingFunction;
 
 using mozilla::dom::StyleChildrenIterator;
 using mozilla::ServoElementSnapshot;
 
 typedef nsINode RawGeckoNode;
 typedef mozilla::dom::Element RawGeckoElement;
 typedef nsIDocument RawGeckoDocument;
 typedef nsPresContext RawGeckoPresContext;
@@ -116,16 +117,18 @@ DECL_BORROWED_REF_TYPE_FOR(nsCSSValue)
 DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSValue)
 DECL_OWNED_REF_TYPE_FOR(RawGeckoPresContext)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoPresContext)
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoAnimationValueList)
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoKeyframeList)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoKeyframeList)
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoComputedKeyframeValuesList)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoStyleAnimationList)
+DECL_BORROWED_MUT_REF_TYPE_FOR(nsTimingFunction)
+DECL_BORROWED_REF_TYPE_FOR(nsTimingFunction)
 
 #undef DECL_ARC_REF_TYPE_FOR
 #undef DECL_OWNED_REF_TYPE_FOR
 #undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
 #undef DECL_BORROWED_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
 #undef DECL_BORROWED_MUT_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -139,17 +139,17 @@ void Gecko_LoadStyleSheet(mozilla::css::
 // optional heap-allocated iterator for nodes that need it. If the creation
 // method returns null, Servo falls back to the aforementioned simpler (and
 // faster) sibling traversal.
 StyleChildrenIteratorOwnedOrNull Gecko_MaybeCreateStyleChildrenIterator(RawGeckoNodeBorrowed node);
 void Gecko_DropStyleChildrenIterator(StyleChildrenIteratorOwned it);
 RawGeckoNodeBorrowedOrNull Gecko_GetNextStyleChild(StyleChildrenIteratorBorrowedMut it);
 
 // Selector Matching.
-uint16_t Gecko_ElementState(RawGeckoElementBorrowed element);
+uint64_t Gecko_ElementState(RawGeckoElementBorrowed element);
 bool Gecko_IsLink(RawGeckoElementBorrowed element);
 bool Gecko_IsTextNode(RawGeckoNodeBorrowed node);
 bool Gecko_IsVisitedLink(RawGeckoElementBorrowed element);
 bool Gecko_IsUnvisitedLink(RawGeckoElementBorrowed element);
 bool Gecko_IsRootElement(RawGeckoElementBorrowed element);
 bool Gecko_MatchesElement(mozilla::CSSPseudoClassType type, RawGeckoElementBorrowed element);
 nsIAtom* Gecko_LocalName(RawGeckoElementBorrowed element);
 nsIAtom* Gecko_Namespace(RawGeckoElementBorrowed element);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -722,19 +722,20 @@ ServoStyleSet::FillKeyframesForName(cons
   return Servo_StyleSet_FillKeyframesForName(mRawSet.get(),
                                              &name,
                                              &aTimingFunction,
                                              aComputedValues,
                                              &aKeyframes);
 }
 
 nsTArray<ComputedKeyframeValues>
-ServoStyleSet::GetComputedKeyframeValuesFor(const nsTArray<Keyframe>& aKeyframes,
-                                            dom::Element* aElement,
-                                            const ServoComputedStyleValues& aServoValues)
+ServoStyleSet::GetComputedKeyframeValuesFor(
+  const nsTArray<Keyframe>& aKeyframes,
+  dom::Element* aElement,
+  const ServoComputedValuesWithParent& aServoValues)
 {
   nsTArray<ComputedKeyframeValues> result(aKeyframes.Length());
 
   // Construct each nsTArray<PropertyStyleAnimationValuePair> here.
   result.AppendElements(aKeyframes.Length());
 
   Servo_GetComputedKeyframeValues(&aKeyframes,
                                   aServoValues.mCurrentStyle,
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -23,17 +23,17 @@
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 class CSSStyleSheet;
 class ServoRestyleManager;
 class ServoStyleSheet;
 struct Keyframe;
-struct ServoComputedStyleValues;
+struct ServoComputedValuesWithParent;
 } // namespace mozilla
 class nsIDocument;
 class nsStyleContext;
 class nsPresContext;
 struct nsTimingFunction;
 struct TreeMatchContext;
 
 namespace mozilla {
@@ -240,17 +240,18 @@ public:
   bool FillKeyframesForName(const nsString& aName,
                             const nsTimingFunction& aTimingFunction,
                             const ServoComputedValues* aComputedValues,
                             nsTArray<Keyframe>& aKeyframes);
 
   nsTArray<ComputedKeyframeValues>
   GetComputedKeyframeValuesFor(const nsTArray<Keyframe>& aKeyframes,
                                dom::Element* aElement,
-                               const ServoComputedStyleValues& aServoValues);
+                               const ServoComputedValuesWithParent&
+                                 aServoValues);
 
 private:
   already_AddRefed<nsStyleContext> GetContext(already_AddRefed<ServoComputedValues>,
                                               nsStyleContext* aParentContext,
                                               nsIAtom* aPseudoTag,
                                               CSSPseudoElementType aPseudoType,
                                               dom::Element* aElementForAnimation);
 
--- a/layout/svg/nsSVGGradientFrame.cpp
+++ b/layout/svg/nsSVGGradientFrame.cpp
@@ -267,17 +267,17 @@ nsSVGGradientFrame::GetPaintServerPatter
     }
   }
 
   if (!patternMatrix.Invert()) {
     return nullptr;
   }
 
   RefPtr<gfxPattern> gradient = CreateGradient();
-  if (!gradient || gradient->CairoStatus())
+  if (!gradient)
     return nullptr;
 
   uint16_t aSpread = GetSpreadMethod();
   if (aSpread == SVG_SPREADMETHOD_PAD)
     gradient->SetExtend(ExtendMode::CLAMP);
   else if (aSpread == SVG_SPREADMETHOD_REFLECT)
     gradient->SetExtend(ExtendMode::REFLECT);
   else if (aSpread == SVG_SPREADMETHOD_REPEAT)
--- a/layout/svg/nsSVGPatternFrame.cpp
+++ b/layout/svg/nsSVGPatternFrame.cpp
@@ -728,17 +728,17 @@ nsSVGPatternFrame::GetPaintServerPattern
                  aFillOrStroke, aGraphicOpacity, aOverrideBounds);
 
   if (!surface) {
     return nullptr;
   }
 
   RefPtr<gfxPattern> pattern = new gfxPattern(surface, pMatrix);
 
-  if (!pattern || pattern->CairoStatus())
+  if (!pattern)
     return nullptr;
 
   pattern->SetExtend(ExtendMode::REPEAT);
   return pattern.forget();
 }
 
 // -------------------------------------------------------------------------
 // Public functions
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -200,16 +200,20 @@ nsXULPopupManager::GetInstance()
   MOZ_ASSERT(sInstance);
   return sInstance;
 }
 
 bool
 nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush,
                           const nsIntPoint* pos, nsIContent** aLastRolledUp)
 {
+  if (aLastRolledUp) {
+    *aLastRolledUp = nullptr;
+  }
+
   // We can disable the autohide behavior via a pref to ease debugging.
   if (nsXULPopupManager::sDevtoolsDisableAutoHide) {
     // Required on linux to allow events to work on other targets.
     if (mWidget) {
       mWidget->CaptureRollupEvents(nullptr, false);
     }
     return false;
   }
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -1370,20 +1370,20 @@ TEST_P(JsepSessionTest, RenegotiationBot
   if (GetParam() == "datachannel") {
     return;
   }
 
   OfferAnswer();
 
   auto oHasStream = HasMediaStream(mSessionOff.GetLocalTracks());
   auto aHasStream = HasMediaStream(mSessionAns.GetLocalTracks());
-  ASSERT_EQ(oHasStream, GetLocalUniqueStreamIds(mSessionOff).size());
-  ASSERT_EQ(aHasStream, GetLocalUniqueStreamIds(mSessionAns).size());
-  ASSERT_EQ(aHasStream, GetRemoteUniqueStreamIds(mSessionOff).size());
-  ASSERT_EQ(oHasStream, GetRemoteUniqueStreamIds(mSessionAns).size());
+  ASSERT_EQ(oHasStream, GetLocalUniqueStreamIds(mSessionOff).size() > 0);
+  ASSERT_EQ(aHasStream, GetLocalUniqueStreamIds(mSessionAns).size() > 0);
+  ASSERT_EQ(aHasStream, GetRemoteUniqueStreamIds(mSessionOff).size()> 0);
+  ASSERT_EQ(oHasStream, GetRemoteUniqueStreamIds(mSessionAns).size() > 0);
 
   auto firstOffId = GetFirstLocalStreamId(mSessionOff);
   auto firstAnsId = GetFirstLocalStreamId(mSessionAns);
 
   auto offererPairs = GetTrackPairsByLevel(mSessionOff);
   auto answererPairs = GetTrackPairsByLevel(mSessionAns);
 
   std::vector<SdpMediaSection::MediaType> extraTypes;
@@ -1393,20 +1393,20 @@ TEST_P(JsepSessionTest, RenegotiationBot
   AddTracksToStream(mSessionAns, firstAnsId, extraTypes);
   types.insert(types.end(), extraTypes.begin(), extraTypes.end());
 
   OfferAnswer(CHECK_SUCCESS);
 
   oHasStream = HasMediaStream(mSessionOff.GetLocalTracks());
   aHasStream = HasMediaStream(mSessionAns.GetLocalTracks());
 
-  ASSERT_EQ(oHasStream, GetLocalUniqueStreamIds(mSessionOff).size());
-  ASSERT_EQ(aHasStream, GetLocalUniqueStreamIds(mSessionAns).size());
-  ASSERT_EQ(aHasStream, GetRemoteUniqueStreamIds(mSessionOff).size());
-  ASSERT_EQ(oHasStream, GetRemoteUniqueStreamIds(mSessionAns).size());
+  ASSERT_EQ(oHasStream, GetLocalUniqueStreamIds(mSessionOff).size() > 0);
+  ASSERT_EQ(aHasStream, GetLocalUniqueStreamIds(mSessionAns).size() > 0);
+  ASSERT_EQ(aHasStream, GetRemoteUniqueStreamIds(mSessionOff).size() > 0);
+  ASSERT_EQ(oHasStream, GetRemoteUniqueStreamIds(mSessionAns).size() > 0);
   if (oHasStream) {
     ASSERT_STREQ(firstOffId.c_str(),
                  GetFirstLocalStreamId(mSessionOff).c_str());
   }
   if (aHasStream) {
     ASSERT_STREQ(firstAnsId.c_str(),
                  GetFirstLocalStreamId(mSessionAns).c_str());
   }
--- a/media/webrtc/signaling/gtest/moz.build
+++ b/media/webrtc/signaling/gtest/moz.build
@@ -1,16 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # TODO: bug 1172551 - get these tests working on iOS
-if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'uikit':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'uikit':
     LOCAL_INCLUDES += [
       '/ipc/chromium/src',
       '/media/mtransport',
       '/media/webrtc/',
       '/media/webrtc/signaling/src/common/time_profiling',
       '/media/webrtc/signaling/src/peerconnection',
     ]
 
@@ -25,8 +25,11 @@ if CONFIG['OS_TARGET'] != 'WINNT' and CO
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-error=shadow']
 
 if CONFIG['_MSC_VER']:
     # This is intended as a temporary workaround to enable warning free building
     # with VS2015.
     # reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size
     CXXFLAGS += ['-wd4312']
+
+    # Disable warning for decorated name length exceeded, name was truncated
+    CXXFLAGS += ['-wd4503']
--- a/media/webrtc/signaling/gtest/sdp_unittests.cpp
+++ b/media/webrtc/signaling/gtest/sdp_unittests.cpp
@@ -327,23 +327,23 @@ TEST_F(SdpTest, parseRtcpFbNackAppFooBar
 TEST_F(SdpTest, parseRtcpFbNackFooBarBaz) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack foo bar baz\r\n");
   ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
             SDP_RTCP_FB_NACK_UNKNOWN);
 }
 
 TEST_F(SdpTest, parseRtcpFbRemb) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 goog-remb\r\n");
-  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+  ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
 }
 
 TEST_F(SdpTest, parseRtcpRbRembAllPt) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:* goog-remb\r\n");
-  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, SDP_ALL_PAYLOADS),
-                                              true);
+  ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, SDP_ALL_PAYLOADS),
+                                                    true);
 }
 
 TEST_F(SdpTest, parseRtcpFbTrrInt0) {
   ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 0\r\n");
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
 }
 
 TEST_F(SdpTest, parseRtcpFbTrrInt123) {
@@ -478,18 +478,18 @@ TEST_F(SdpTest, parseRtcpFbKitchenSink) 
             SDP_RTCP_FB_NACK_UNKNOWN);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 9),
             SDP_RTCP_FB_NACK_NOT_FOUND);
 
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 2), 123U);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 3), 0xFFFFFFFF);
 
-  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
-  ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 2, 120), false);
+  ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+  ASSERT_EQ((bool)sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 2, 120), false);
 
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_CCM_FIR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 2),
             SDP_RTCP_FB_CCM_TMMBR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 3),
             SDP_RTCP_FB_CCM_TMMBR);
   ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 4),
             SDP_RTCP_FB_CCM_TSTR);
--- a/media/webrtc/trunk/webrtc/voice_engine/channel.cc
+++ b/media/webrtc/trunk/webrtc/voice_engine/channel.cc
@@ -3166,17 +3166,17 @@ Channel::GetRTPStatistics(
     }
 
     ChannelStatistics stats = statistics_proxy_->GetStats();
     const int32_t playoutFrequency = audio_coding_->PlayoutFrequency();
     if (playoutFrequency > 0) {
       // Scale RTP statistics given the current playout frequency
       maxJitterMs = stats.max_jitter / (playoutFrequency / 1000);
       averageJitterMs = stats.rtcp.jitter / (playoutFrequency / 1000);
-      cumulativeLost = stats.cumulative_lost;
+      cumulativeLost = stats.rtcp.cumulative_lost;
     }
 
     discardedPackets = _numberOfDiscardedPackets;
 
     return 0;
 }
 
 int Channel::GetRemoteRTCPReportBlocks(
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java
@@ -10,16 +10,17 @@ import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.TabThumbnailWrapper;
 import org.mozilla.gecko.widget.TouchDelegateWithReset;
 import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
 
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v4.widget.TextViewCompat;
+import android.support.v7.widget.ViewUtils;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.Checkable;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -104,29 +105,35 @@ public class TabsLayoutItemView extends 
             @Override
             public boolean onPreDraw() {
                 getViewTreeObserver().removeOnPreDrawListener(this);
 
                 // Ideally we want the close button hit area to be 40x40dp but we are constrained by the height of the parent, so
                 // we make it as tall as the parent view and 40dp across.
                 final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());;
 
-                final Rect hitRect = new Rect();
-                hitRect.top = 0;
-                hitRect.right = getWidth();
-                hitRect.left = getWidth() - targetHitArea;
-                hitRect.bottom = targetHitArea;
+                final Rect hitRect = getHitRectRelatively(targetHitArea);
 
                 setTouchDelegate(new TouchDelegateWithReset(hitRect, mCloseButton));
 
                 return true;
             }
         });
     }
 
+    private Rect getHitRectRelatively(int targetHitArea) {
+        final boolean isRtl = ViewUtils.isLayoutRtl(this);
+        final Rect hitRect = new Rect();
+        hitRect.top = 0;
+        hitRect.right = isRtl ? targetHitArea : getWidth();
+        hitRect.left = isRtl ? 0 : getWidth() - targetHitArea;
+        hitRect.bottom = targetHitArea;
+        return hitRect;
+    }
+
     protected void assignValues(Tab tab)  {
         if (tab == null) {
             return;
         }
 
         mTabId = tab.getId();
 
         setChecked(Tabs.getInstance().isSelectedTab(tab));
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -113,16 +113,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebsiteMetadata", "resource://gre/modules/WebsiteMetadata.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm");
+
 XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
   "@mozilla.org/gfx/fontenumerator;1",
   "nsIFontEnumerator");
 
 var GlobalEventDispatcher = EventDispatcher.instance;
 var WindowEventDispatcher = EventDispatcher.for(window);
 
 var lazilyLoadedBrowserScripts = [
@@ -1503,16 +1505,19 @@ var BrowserApp = {
       return this.PREF_TRACKING_PROTECTION_ENABLED_PB;
     }
     return this.PREF_TRACKING_PROTECTION_DISABLED;
   },
 
   sanitize: function (aItems, callback, aShutdown) {
     let success = true;
     var promises = [];
+    let refObj = {};
+
+    TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
 
     for (let key in aItems) {
       if (!aItems[key])
         continue;
 
       key = key.replace("private.data.", "");
 
       switch (key) {
@@ -1527,26 +1532,28 @@ var BrowserApp = {
           }
           // fall-through if aShutdown is false
         default:
           promises.push(Sanitizer.clearItem(key));
       }
     }
 
     Promise.all(promises).then(function() {
+      TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
       GlobalEventDispatcher.sendRequest({
         type: "Sanitize:Finished",
         success: true,
         shutdown: aShutdown === true
       });
 
       if (callback) {
         callback();
       }
     }).catch(function(err) {
+      TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
       GlobalEventDispatcher.sendRequest({
         type: "Sanitize:Finished",
         error: err,
         success: false,
         shutdown: aShutdown === true
       });
 
       if (callback) {
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -17,16 +17,18 @@ Cu.import("resource://gre/modules/Task.j
 Cu.import("resource://gre/modules/Downloads.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Accounts.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
                                   "resource://gre/modules/DownloadIntegration.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
                                   "resource://gre/modules/Messaging.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+                                  "resource://gre/modules/TelemetryStopwatch.jsm");
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 this.EXPORTED_SYMBOLS = ["Sanitizer"];
 
 function Sanitizer() {}
@@ -45,54 +47,66 @@ Sanitizer.prototype = {
     }
   },
 
   items: {
     cache: {
       clear: function ()
       {
         return new Promise(function(resolve, reject) {
+          let refObj = {};
+          TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
+
           var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
           try {
             cache.clear();
           } catch(er) {}
 
           let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                                                            .getImgCacheForDocument(null);
           try {
             imageCache.clearCache(false); // true=chrome, false=content
           } catch(er) {}
 
+          TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
           resolve();
         });
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     cookies: {
       clear: function ()
       {
         return new Promise(function(resolve, reject) {
+          let refObj = {};
+          TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
+
           Services.cookies.removeAll();
+
+          TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
           resolve();
         });
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     siteSettings: {
       clear: Task.async(function* () {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
+
         // Clear site-specific permissions like "Allow this site to open popups"
         Services.perms.removeAll();
 
         // Clear site-specific settings like page-zoom level
         Cc["@mozilla.org/content-pref/service;1"]
           .getService(Ci.nsIContentPrefService2)
           .removeAllDomains(null);
 
@@ -109,16 +123,17 @@ Sanitizer.prototype = {
             if (Components.isSuccessCode(status)) {
               resolve();
             } else {
               reject(new Error("Error clearing push subscriptions: " +
                                status));
             }
           });
         });
+        TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
       }),
 
       get canClear()
       {
         return true;
       }
     },
 
@@ -140,19 +155,23 @@ Sanitizer.prototype = {
       {
           return true;
       }
     },
 
     history: {
       clear: function ()
       {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
+
         return EventDispatcher.instance.sendRequestForResult({ type: "Sanitize:ClearHistory" })
           .catch(e => Cu.reportError("Java-side history clearing failed: " + e))
           .then(function() {
+            TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
             try {
               Services.obs.notifyObservers(null, "browser:purge-session-history", "");
             }
             catch (e) { }
 
             try {
               var predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
               predictor.reset();
@@ -166,24 +185,28 @@ Sanitizer.prototype = {
         // the browser:purge-session-history notification. (like error console)
         return true;
       }
     },
 
     openTabs: {
       clear: function ()
       {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
+
         return EventDispatcher.instance.sendRequestForResult({ type: "Sanitize:OpenTabs" })
           .catch(e => Cu.reportError("Java-side tab clearing failed: " + e))
           .then(function() {
             try {
               // clear "Recently Closed" tabs in Android App
               Services.obs.notifyObservers(null, "browser:purge-session-tabs", "");
             }
             catch (e) { }
+            TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
           });
       },
 
       get canClear()
       {
         return true;
       }
     },
@@ -200,17 +223,22 @@ Sanitizer.prototype = {
         return true;
       }
     },
 
     formdata: {
       clear: function ()
       {
         return new Promise(function(resolve, reject) {
+          let refObj = {};
+          TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
+
           FormHistory.update({ op: "remove" });
+
+          TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
           resolve();
         });
       },
 
       canClear: function (aCallback)
       {
         let count = 0;
         let countDone = {
@@ -219,16 +247,19 @@ Sanitizer.prototype = {
           handleCompletion: function(aReason) { aCallback(aReason == 0 && count > 0); }
         };
         FormHistory.count({}, countDone);
       }
     },
 
     downloadFiles: {
       clear: Task.async(function* () {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
+
         let list = yield Downloads.getList(Downloads.ALL);
         let downloads = yield list.getAll();
         var finalizePromises = [];
 
         // Logic copied from DownloadList.removeFinished. Ideally, we would
         // just use that method directly, but we want to be able to remove the
         // downloaded files as well.
         for (let download of downloads) {
@@ -251,16 +282,17 @@ Sanitizer.prototype = {
                 Cu.reportError(ex);
               }
             });
           }
         }
 
         yield Promise.all(finalizePromises);
         yield DownloadIntegration.forceSave();
+        TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
       }),
 
       get canClear()
       {
         return true;
       }
     },
 
@@ -279,23 +311,27 @@ Sanitizer.prototype = {
         return (count > 0);
       }
     },
 
     sessions: {
       clear: function ()
       {
         return new Promise(function(resolve, reject) {
+          let refObj = {};
+          TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
+
           // clear all auth tokens
           var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
           sdr.logoutAndTeardown();
 
           // clear FTP and plain HTTP auth sessions
           Services.obs.notifyObservers(null, "net:clear-active-logins", null);
 
+          TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
           resolve();
         });
       },
 
       get canClear()
       {
         return true;
       }
--- a/netwerk/base/nsProtocolProxyService.cpp
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -31,16 +31,17 @@
 #include "nsPACMan.h"
 #include "nsProxyRelease.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/CondVar.h"
 #include "nsISystemProxySettings.h"
 #include "nsINetworkLinkService.h"
 #include "nsIHttpChannelInternal.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Tokenizer.h"
 
 //----------------------------------------------------------------------------
 
 namespace mozilla {
 namespace net {
 
   extern const char kProxyType_HTTP[];
   extern const char kProxyType_HTTPS[];
@@ -641,17 +642,17 @@ nsProtocolProxyService::PrefsChanged(nsI
     if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout")))
         proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
                          mFailedProxyTimeout);
 
     if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
         rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"),
                                      getter_Copies(tempString));
         if (NS_SUCCEEDED(rv))
-            LoadHostFilters(tempString.get());
+            LoadHostFilters(tempString);
     }
 
     // We're done if not using something that could give us a PAC URL
     // (PAC, WPAD or System)
     if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
         mProxyConfig != PROXYCONFIG_SYSTEM)
         return;
 
@@ -1465,80 +1466,139 @@ nsProtocolProxyService::UnregisterChanne
 NS_IMETHODIMP
 nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType)
 {
   *aProxyConfigType = mProxyConfig;
   return NS_OK;
 }
 
 void
-nsProtocolProxyService::LoadHostFilters(const char *filters)
+nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters)
 {
     // check to see the owners flag? /!?/ TODO
     if (mHostFiltersArray.Length() > 0) {
         mHostFiltersArray.Clear();
     }
 
-    if (!filters)
-        return; // fail silently...
+    if (aFilters.IsEmpty()) {
+        return;
+    }
 
     //
     // filter  = ( host | domain | ipaddr ["/" mask] ) [":" port]
     // filters = filter *( "," LWS filter)
     //
     // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref string
     mFilterLocalHosts = false;
-    while (*filters) {
+
+    mozilla::Tokenizer t(aFilters);
+    mozilla::Tokenizer::Token token;
+    bool eof = false;
+    // while (*filters) {
+    while (!eof) {
         // skip over spaces and ,
-        while (*filters && (*filters == ',' || IS_ASCII_SPACE(*filters)))
-            filters++;
-
-        const char *starthost = filters;
-        const char *endhost = filters + 1; // at least that...
-        const char *portLocation = 0;
-        const char *maskLocation = 0;
-
-        while (*endhost && (*endhost != ',' && !IS_ASCII_SPACE(*endhost))) {
-            if (*endhost == ':')
-                portLocation = endhost;
-            else if (*endhost == '/')
-                maskLocation = endhost;
-            else if (*endhost == ']') // IPv6 address literals
-                portLocation = 0;
-            endhost++;
+        t.SkipWhites();
+        while (t.CheckChar(',')) {
+            t.SkipWhites();
         }
 
-        filters = endhost; // advance iterator up front
+        nsAutoCString portStr;
+        nsAutoCString hostStr;
+        nsAutoCString maskStr;
+        t.Record();
+
+        bool parsingIPv6 = false;
+        bool parsingPort = false;
+        bool parsingMask = false;
+        while (t.Next(token)) {
+            if (token.Equals(mozilla::Tokenizer::Token::EndOfFile()))  {
+                eof = true;
+                break;
+            }
+            if (token.Equals(mozilla::Tokenizer::Token::Char(',')) ||
+                token.Type() == mozilla::Tokenizer::TOKEN_WS) {
+                break;
+            }
+
+            if (token.Equals(mozilla::Tokenizer::Token::Char('['))) {
+                parsingIPv6 = true;
+                continue;
+            }
 
-        // locate end of host
-        const char *end = maskLocation ? maskLocation :
-                          portLocation ? portLocation :
-                          endhost;
+            if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) {
+                // Port is starting. Claim the previous as host.
+                if (parsingMask) {
+                    t.Claim(maskStr);
+                } else {
+                    t.Claim(hostStr);
+                }
+                t.Record();
+                parsingPort = true;
+                continue;
+            } else if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) {
+                t.Claim(hostStr);
+                t.Record();
+                parsingMask = true;
+                continue;
+            } else if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) {
+                parsingIPv6 = false;
+                continue;
+            }
+        }
+        if (!parsingPort && !parsingMask) {
+            t.Claim(hostStr);
+        } else if (parsingPort) {
+            t.Claim(portStr);
+        } else if (parsingMask) {
+            t.Claim(maskStr);
+        } else {
+            NS_WARNING("Could not parse this rule");
+            continue;
+        }
 
-        nsAutoCString str(starthost, end - starthost);
+        if (hostStr.IsEmpty()) {
+            continue;
+        }
 
         // If the current host filter is "<local>", then all local (i.e.
         // no dots in the hostname) hosts should bypass the proxy
-        if (str.EqualsIgnoreCase("<local>")) {
+        if (hostStr.EqualsIgnoreCase("<local>")) {
             mFilterLocalHosts = true;
             LOG(("loaded filter for local hosts "
                  "(plain host names, no dots)\n"));
             // Continue to next host filter;
             continue;
         }
 
         // For all other host filters, create HostInfo object and add to list
         HostInfo *hinfo = new HostInfo();
-        hinfo->port = portLocation ? atoi(portLocation + 1) : 0;
+        nsresult rv = NS_OK;
+
+        int32_t port = portStr.ToInteger(&rv);
+        if (NS_FAILED(rv)) {
+            port = 0;
+        }
+        hinfo->port = port;
+
+        int32_t maskLen = maskStr.ToInteger(&rv);
+        if (NS_FAILED(rv)) {
+            maskLen = 128;
+        }
+
+        // PR_StringToNetAddr can't parse brackets enclosed IPv6
+        nsAutoCString addrString = hostStr;
+        if (hostStr.First() == '[' && hostStr.Last() == ']') {
+            addrString = Substring(hostStr, 1, hostStr.Length() - 2);
+        }
 
         PRNetAddr addr;
-        if (PR_StringToNetAddr(str.get(), &addr) == PR_SUCCESS) {
+        if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) {
             hinfo->is_ipaddr   = true;
             hinfo->ip.family   = PR_AF_INET6; // we always store address as IPv6
-            hinfo->ip.mask_len = maskLocation ? atoi(maskLocation + 1) : 128;
+            hinfo->ip.mask_len = maskLen;
 
             if (hinfo->ip.mask_len == 0) {
                 NS_WARNING("invalid mask");
                 goto loser;
             }
 
             if (addr.raw.family == PR_AF_INET) {
                 // convert to IPv4-mapped address
@@ -1555,37 +1615,43 @@ nsProtocolProxyService::LoadHostFilters(
                 NS_WARNING("unknown address family");
                 goto loser;
             }
 
             // apply mask to IPv6 address
             proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len);
         }
         else {
-            uint32_t startIndex, endIndex;
-            if (str.First() == '*')
-                startIndex = 1; // *.domain -> .domain
-            else
-                startIndex = 0;
-            endIndex = (portLocation ? portLocation : endhost) - starthost;
+            nsAutoCString host;
+            if (hostStr.First() == '*') {
+                host = Substring(hostStr, 1);
+            } else {
+                host = hostStr;
+            }
+
+            if (host.IsEmpty()) {
+                hinfo->name.host = nullptr;
+                goto loser;
+            }
+
+            hinfo->name.host_len = host.Length();
 
             hinfo->is_ipaddr = false;
-            hinfo->name.host = ToNewCString(Substring(str, startIndex, endIndex));
+            hinfo->name.host = ToNewCString(host);
 
             if (!hinfo->name.host)
                 goto loser;
-
-            hinfo->name.host_len = endIndex - startIndex;
         }
 
 //#define DEBUG_DUMP_FILTERS
 #ifdef DEBUG_DUMP_FILTERS
-        printf("loaded filter[%u]:\n", mHostFiltersArray.Length());
+        printf("loaded filter[%zu]:\n", mHostFiltersArray.Length());
         printf("  is_ipaddr = %u\n", hinfo->is_ipaddr);
         printf("  port = %u\n", hinfo->port);
+        printf("  host = %s\n", hostStr.get());
         if (hinfo->is_ipaddr) {
             printf("  ip.family = %x\n", hinfo->ip.family);
             printf("  ip.mask_len = %u\n", hinfo->ip.mask_len);
 
             PRNetAddr netAddr;
             PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr);
             memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr));
 
--- a/netwerk/base/nsProtocolProxyService.h
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -49,16 +49,17 @@ public:
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
 
     nsProtocolProxyService();
 
     nsresult Init();
 
 protected:
     friend class nsAsyncResolveRequest;
+    friend class TestProtocolProxyService_LoadHostFilters_Test; // for gtest
 
     ~nsProtocolProxyService();
 
     /**
      * This method is called whenever a preference may have changed or
      * to initialize all preferences.
      *
      * @param prefs
@@ -268,17 +269,17 @@ protected:
                                     nsIProxyInfo **proxyInfo);
 
     /**
      * This method populates mHostFiltersArray from the given string.
      *
      * @param hostFilters
      *        A "no-proxy-for" exclusion list.
      */
-    void LoadHostFilters(const char *hostFilters);
+    void LoadHostFilters(const nsACString& hostFilters);
 
     /**
      * This method checks the given URI against mHostFiltersArray.
      *
      * @param uri
      *        The URI to test.
      * @param defaultPort
      *        The default port for the given URI.
new file mode 100644
--- /dev/null
+++ b/netwerk/test/gtest/TestProtocolProxyService.cpp
@@ -0,0 +1,128 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "../../base/nsProtocolProxyService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace net {
+
+TEST(TestProtocolProxyService, LoadHostFilters) {
+  nsCOMPtr<nsIProtocolProxyService2> ps = do_GetService(NS_PROTOCOLPROXYSERVICE_CID);
+  ASSERT_TRUE(ps);
+  mozilla::net::nsProtocolProxyService* pps = static_cast<mozilla::net::nsProtocolProxyService*>(ps.get());
+
+  nsCOMPtr<nsIURL> url( do_CreateInstance(NS_STANDARDURL_CONTRACTID) );
+  ASSERT_TRUE(url) << "couldn't create URL";
+
+  nsAutoCString spec;
+
+  auto CheckLoopbackURLs = [&](bool expected)
+  {
+    // loopback IPs are always filtered
+    spec = "http://127.0.0.1";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+    spec = "http://[::1]";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+  };
+
+  auto CheckURLs = [&](bool expected)
+  {
+    spec = "http://example.com";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+    spec = "https://10.2.3.4";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 443), expected);
+
+    spec = "http://1.2.3.4";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+    spec = "http://1.2.3.4:8080";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+    spec = "http://[2001::1]";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+    spec = "http://2.3.4.5:7777";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+    spec = "http://[abcd::2]:123";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+
+    spec = "http://bla.test.com";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+  };
+
+  auto CheckPortDomain = [&](bool expected)
+  {
+    spec = "http://blabla.com:10";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+  };
+
+  auto CheckLocalDomain = [&](bool expected)
+  {
+    spec = "http://test";
+    ASSERT_EQ(url->SetSpec(spec), NS_OK);
+    ASSERT_EQ(pps->CanUseProxy(url, 80), expected);
+  };
+
+  // --------------------------------------------------------------------------
+
+  nsAutoCString filter;
+
+  // Anything is allowed when there are no filters set
+  printf("Testing empty filter: %s\n", filter.get());
+  pps->LoadHostFilters(filter);
+
+  CheckLoopbackURLs(true); // only time when loopbacks can be proxied. bug?
+  CheckLocalDomain(true);
+  CheckURLs(true);
+  CheckPortDomain(true);
+
+  // --------------------------------------------------------------------------
+
+  filter = "example.com, 1.2.3.4/16, [2001::1], 10.0.0.0/8, 2.3.0.0/16:7777, [abcd::1]/64:123, *.test.com";
+  printf("Testing filter: %s\n", filter.get());
+  pps->LoadHostFilters(filter);
+  // Check URLs can no longer use filtered proxy
+  CheckURLs(false);
+  CheckLoopbackURLs(false);
+  CheckLocalDomain(true);
+  CheckPortDomain(true);
+
+  // --------------------------------------------------------------------------
+
+  // This is space separated. See bug 1346711 comment 4. We check this to keep
+  // backwards compatibility.
+  filter = "<local> blabla.com:10";
+  printf("Testing filter: %s\n", filter.get());
+  pps->LoadHostFilters(filter);
+  CheckURLs(true);
+  CheckLoopbackURLs(false);
+  CheckLocalDomain(false);
+  CheckPortDomain(false);
+
+  // Check that we don't crash on weird input
+  filter = "a b c abc:1x2, ,, * ** *.* *:10 :20 :40/12 */12:90";
+  printf("Testing filter: %s\n", filter.get());
+  pps->LoadHostFilters(filter);
+}
+
+} // namespace net
+} // namespace mozila
--- a/netwerk/test/gtest/moz.build
+++ b/netwerk/test/gtest/moz.build
@@ -1,13 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
+    'TestProtocolProxyService.cpp',
     'TestStandardURL.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul-gtest'
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -246,17 +246,17 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "alloc-no-stdlib 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "browserhtml"
 version = "0.1.17"
-source = "git+https://github.com/browserhtml/browserhtml?branch=crate#cd5374a970fd404377ab418ac1e4a358db47691f"
+source = "git+https://github.com/browserhtml/browserhtml?branch=crate#7c66ae9a3e29d35230d5b9f16d19a562b1312c87"
 
 [[package]]
 name = "build-apk"
 version = "0.0.1"
 dependencies = [
  "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
--- a/servo/README.md
+++ b/servo/README.md
@@ -54,16 +54,18 @@ If you've already partially compiled ser
 ``` sh
 sudo apt install git curl freeglut3-dev autoconf \
     libfreetype6-dev libgl1-mesa-dri libglib2.0-dev xorg-dev \
     gperf g++ build-essential cmake virtualenv python-pip \
     libssl1.0-dev libbz2-dev libosmesa6-dev libxmu6 libxmu-dev \
     libglu1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev
 ```
 
+If you using a version prior to **Ubuntu 17.04** or **Debian Sid**, replace `libssl1.0-dev` with `libssl-dev`.
+
 If you are on **Ubuntu 14.04** and encountered errors on installing these dependencies involving `libcheese`, see [#6158](https://github.com/servo/servo/issues/6158) for a workaround.
 
 If `virtualenv` does not exist, try `python-virtualenv`.
 
 #### On Fedora
 
 ``` sh
 sudo dnf install curl freeglut-devel libtool gcc-c++ libXi-devel \
--- a/servo/components/bluetooth/lib.rs
+++ b/servo/components/bluetooth/lib.rs
@@ -2,32 +2,34 @@
  * 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/. */
 
 #[macro_use]
 extern crate bitflags;
 extern crate bluetooth_traits;
 extern crate device;
 extern crate ipc_channel;
+#[cfg(target_os = "linux")]
 extern crate servo_config;
 extern crate servo_rand;
 #[cfg(target_os = "linux")]
 extern crate tinyfiledialogs;
 extern crate uuid;
 
 pub mod test;
 
 use bluetooth_traits::{BluetoothCharacteristicMsg, BluetoothDescriptorMsg, BluetoothServiceMsg};
 use bluetooth_traits::{BluetoothDeviceMsg, BluetoothRequest, BluetoothResponse, GATTType};
 use bluetooth_traits::{BluetoothError, BluetoothResponseResult, BluetoothResult};
 use bluetooth_traits::blocklist::{uuid_is_blocklisted, Blocklist};
 use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSequence, RequestDeviceoptions};
 use device::bluetooth::{BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic};
 use device::bluetooth::{BluetoothGATTDescriptor, BluetoothGATTService};
 use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
+#[cfg(target_os = "linux")]
 use servo_config::opts;
 use servo_rand::Rng;
 use std::borrow::ToOwned;
 use std::collections::{HashMap, HashSet};
 use std::string::String;
 use std::thread;
 use std::time::Duration;
 
--- a/servo/components/gfx/platform/windows/font.rs
+++ b/servo/components/gfx/platform/windows/font.rs
@@ -367,17 +367,17 @@ impl FontHandleMethods for FontHandle {
             strikeout_offset: au_from_du_s(dm.strikethroughPosition as i32),
             leading:          au_from_du_s(leading as i32),
             x_height:         au_from_du_s(dm.xHeight as i32),
             em_size:          au_from_em(self.em_size as f64),
             ascent:           au_from_du_s(dm.ascent as i32),
             descent:          au_from_du_s(dm.descent as i32),
             max_advance:      au_from_pt(0.0), // FIXME
             average_advance:  au_from_pt(0.0), // FIXME
-            line_gap:         au_from_du(dm.lineGap as i32),
+            line_gap:         au_from_du((dm.ascent + dm.descent + dm.lineGap as u16) as i32),
         };
         debug!("Font metrics (@{} pt): {:?}", self.em_size * 12., metrics);
         metrics
     }
 
     fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
         self.face.get_font_table(tag).map(|bytes| FontTable { data: bytes })
     }
--- a/servo/components/script/dom/permissions.rs
+++ b/servo/components/script/dom/permissions.rs
@@ -12,16 +12,17 @@ use dom::bluetooth::Bluetooth;
 use dom::bluetoothpermissionresult::BluetoothPermissionResult;
 use dom::globalscope::GlobalScope;
 use dom::permissionstatus::PermissionStatus;
 use dom::promise::Promise;
 use dom_struct::dom_struct;
 use js::conversions::ConversionResult;
 use js::jsapi::{JSContext, JSObject};
 use js::jsval::{ObjectValue, UndefinedValue};
+#[cfg(target_os = "linux")]
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use std::rc::Rc;
 #[cfg(target_os = "linux")]
 use tinyfiledialogs::{self, MessageBoxIcon, YesNo};
 
 #[cfg(target_os = "linux")]
 const DIALOG_TITLE: &'static str = "Permission request dialog";
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -686,16 +686,17 @@ mod bindings {
             "RawGeckoElement",
             "RawGeckoDocument",
             "RawServoDeclarationBlockStrong",
             "RawGeckoPresContext",
             "RawGeckoStyleAnimationList",
         ];
         let servo_borrow_types = [
             "nsCSSValue",
+            "nsTimingFunction",
             "RawGeckoAnimationValueList",
             "RawGeckoKeyframeList",
             "RawGeckoComputedKeyframeValuesList",
         ];
         for &ty in structs_types.iter() {
             builder = builder.hide_type(ty)
                 .raw_line(format!("use gecko_bindings::structs::{};", ty));
             // TODO this is hacky, figure out a better way to do it without
--- a/servo/components/style/gecko_bindings/bindings.rs
+++ b/servo/components/style/gecko_bindings/bindings.rs
@@ -258,16 +258,20 @@ pub type RawServoDeclarationBlockStrongB
 pub type RawGeckoPresContextBorrowed<'a> = &'a RawGeckoPresContext;
 pub type RawGeckoPresContextBorrowedOrNull<'a> = Option<&'a RawGeckoPresContext>;
 pub type RawGeckoStyleAnimationListBorrowed<'a> = &'a RawGeckoStyleAnimationList;
 pub type RawGeckoStyleAnimationListBorrowedOrNull<'a> = Option<&'a RawGeckoStyleAnimationList>;
 pub type nsCSSValueBorrowed<'a> = &'a nsCSSValue;
 pub type nsCSSValueBorrowedOrNull<'a> = Option<&'a nsCSSValue>;
 pub type nsCSSValueBorrowedMut<'a> = &'a mut nsCSSValue;
 pub type nsCSSValueBorrowedMutOrNull<'a> = Option<&'a mut nsCSSValue>;
+pub type nsTimingFunctionBorrowed<'a> = &'a nsTimingFunction;
+pub type nsTimingFunctionBorrowedOrNull<'a> = Option<&'a nsTimingFunction>;
+pub type nsTimingFunctionBorrowedMut<'a> = &'a mut nsTimingFunction;
+pub type nsTimingFunctionBorrowedMutOrNull<'a> = Option<&'a mut nsTimingFunction>;
 pub type RawGeckoAnimationValueListBorrowed<'a> = &'a RawGeckoAnimationValueList;
 pub type RawGeckoAnimationValueListBorrowedOrNull<'a> = Option<&'a RawGeckoAnimationValueList>;
 pub type RawGeckoAnimationValueListBorrowedMut<'a> = &'a mut RawGeckoAnimationValueList;
 pub type RawGeckoAnimationValueListBorrowedMutOrNull<'a> = Option<&'a mut RawGeckoAnimationValueList>;
 pub type RawGeckoKeyframeListBorrowed<'a> = &'a RawGeckoKeyframeList;
 pub type RawGeckoKeyframeListBorrowedOrNull<'a> = Option<&'a RawGeckoKeyframeList>;
 pub type RawGeckoKeyframeListBorrowedMut<'a> = &'a mut RawGeckoKeyframeList;
 pub type RawGeckoKeyframeListBorrowedMutOrNull<'a> = Option<&'a mut RawGeckoKeyframeList>;
@@ -1385,17 +1389,17 @@ extern "C" {
 extern "C" {
     pub fn Servo_StyleSet_NoteStyleSheetsChanged(set:
                                                      RawServoStyleSetBorrowed);
 }
 extern "C" {
     pub fn Servo_StyleSet_FillKeyframesForName(set: RawServoStyleSetBorrowed,
                                                property: *const nsACString,
                                                timing_function:
-                                                   *const nsTimingFunction,
+                                                   nsTimingFunctionBorrowed,
                                                computed_values:
                                                    ServoComputedValuesBorrowed,
                                                keyframe_list:
                                                    RawGeckoKeyframeListBorrowedMut)
      -> bool;
 }
 extern "C" {
     pub fn Servo_CssRules_ListTypes(rules: ServoCssRulesBorrowed,
@@ -1483,16 +1487,22 @@ extern "C" {
 extern "C" {
     pub fn Servo_ParseProperty(property: *const nsACString,
                                value: *const nsACString,
                                base: *const nsACString,
                                data: *const GeckoParserExtraData)
      -> RawServoDeclarationBlockStrong;
 }
 extern "C" {
+    pub fn Servo_ParseEasing(easing: *const nsAString,
+                             base: *const nsACString,
+                             data: *const GeckoParserExtraData,
+                             output: nsTimingFunctionBorrowedMut) -> bool;
+}
+extern "C" {
     pub fn Servo_GetComputedKeyframeValues(keyframes:
                                                RawGeckoKeyframeListBorrowed,
                                            style: ServoComputedValuesBorrowed,
                                            parent_style:
                                                ServoComputedValuesBorrowedOrNull,
                                            set: RawServoStyleSetBorrowed,
                                            result:
                                                RawGeckoComputedKeyframeValuesListBorrowedMut);
--- a/servo/components/style/gecko_bindings/nsstring_vendor/src/lib.rs
+++ b/servo/components/style/gecko_bindings/nsstring_vendor/src/lib.rs
@@ -116,16 +116,17 @@
 //! can be useful when implementing FFI types which contain `ns[C]String` members
 //! which invoke their member's destructors through C++ code.
 
 #![allow(non_camel_case_types)]
 #![deny(warnings)]
 
 use std::ops::{Deref, DerefMut};
 use std::marker::PhantomData;
+use std::borrow;
 use std::slice;
 use std::ptr;
 use std::mem;
 use std::fmt;
 use std::cmp;
 use std::str;
 use std::u32;
 use std::os::raw::c_void;
@@ -144,21 +145,30 @@ const F_CLASS_FIXED: u32 = 1 << 16; // i
 
 ////////////////////////////////////
 // Generic String Bindings Macros //
 ////////////////////////////////////
 
 macro_rules! define_string_types {
     {
         char_t = $char_t: ty;
+
         AString = $AString: ident;
         String = $String: ident;
         FixedString = $FixedString: ident;
 
+        StringLike = $StringLike: ident;
+        StringAdapter = $StringAdapter: ident;
+
         StringRepr = $StringRepr: ident;
+
+        drop = $drop: ident;
+        assign = $assign: ident, $fallible_assign: ident;
+        append = $append: ident, $fallible_append: ident;
+        set_length = $set_length: ident, $fallible_set_length: ident;
     } => {
         /// The representation of a ns[C]String type in C++. This type is
         /// used internally by our definition of ns[C]String to ensure layout
         /// compatibility with the C++ ns[C]String type.
         ///
         /// This type may also be used in place of a C++ ns[C]String inside of
         /// struct definitions which are shared with C++, as it has identical
         /// layout to our ns[C]String type.
@@ -203,16 +213,82 @@ macro_rules! define_string_types {
         /// the construction by code outside of this module. It is used instead
         /// of a private `()` member because the `improper_ctypes` lint complains
         /// about some ZST members in `extern "C"` function declarations.
         #[repr(C)]
         pub struct $AString {
             _prohibit_constructor: [u8; 0],
         }
 
+        impl $AString {
+            /// Assign the value of `other` into self, overwriting any value
+            /// currently stored. Performs an optimized assignment when possible
+            /// if `other` is a `nsA[C]String`.
+            pub fn assign<T: $StringLike + ?Sized>(&mut self, other: &T) {
+                unsafe { $assign(self, other.adapt().as_ptr()) };
+            }
+
+            /// Assign the value of `other` into self, overwriting any value
+            /// currently stored. Performs an optimized assignment when possible
+            /// if `other` is a `nsA[C]String`.
+            ///
+            /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+            pub fn fallible_assign<T: $StringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+                if unsafe { $fallible_assign(self, other.adapt().as_ptr()) } {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            }
+
+            /// Append the value of `other` into self.
+            pub fn append<T: $StringLike + ?Sized>(&mut self, other: &T) {
+                unsafe { $append(self, other.adapt().as_ptr()) };
+            }
+
+            /// Append the value of `other` into self.
+            ///
+            /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+            pub fn fallible_append<T: $StringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+                if unsafe { $fallible_append(self, other.adapt().as_ptr()) } {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            }
+
+            /// Set the length of the string to the passed-in length, and expand
+            /// the backing capacity to match. This method is unsafe as it can
+            /// expose uninitialized memory when len is greater than the current
+            /// length of the string.
+            pub unsafe fn set_length(&mut self, len: u32) {
+                $set_length(self, len);
+            }
+
+            /// Set the length of the string to the passed-in length, and expand
+            /// the backing capacity to match. This method is unsafe as it can
+            /// expose uninitialized memory when len is greater than the current
+            /// length of the string.
+            ///
+            /// Returns Ok(()) on success, and Err(()) if the allocation failed.
+            pub unsafe fn fallible_set_length(&mut self, len: u32) -> Result<(), ()> {
+                if $fallible_set_length(self, len) {
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            }
+
+            pub fn truncate(&mut self) {
+                unsafe {
+                    self.set_length(0);
+                }
+            }
+        }
+
         impl Deref for $AString {
             type Target = [$char_t];
             fn deref(&self) -> &[$char_t] {
                 unsafe {
                     // All $AString values point to a struct prefix which is
                     // identical to $StringRepr, this we can transmute `self`
                     // into $StringRepr to get the reference to the underlying
                     // data.
@@ -272,16 +348,24 @@ macro_rules! define_string_types {
                         length: 0,
                         flags: F_NONE,
                     },
                     _marker: PhantomData,
                 }
             }
         }
 
+        impl<'a> Drop for $String<'a> {
+            fn drop(&mut self) {
+                unsafe {
+                    $drop(&mut **self);
+                }
+            }
+        }
+
         impl<'a> Deref for $String<'a> {
             type Target = $AString;
             fn deref(&self) -> &$AString {
                 &self.hdr
             }
         }
 
         impl<'a> DerefMut for $String<'a> {
@@ -308,29 +392,28 @@ macro_rules! define_string_types {
             }
         }
 
         impl<'a> From<&'a [$char_t]> for $String<'a> {
             fn from(s: &'a [$char_t]) -> $String<'a> {
                 assert!(s.len() < (u32::MAX as usize));
                 $String {
                     hdr: $StringRepr {
-                        data: if s.is_empty() { ptr::null() } else { s.as_ptr()},
+                        data: if s.is_empty() { ptr::null() } else { s.as_ptr() },
                         length: s.len() as u32,
                         flags: F_NONE,
                     },
                     _marker: PhantomData,
                 }
             }
         }
 
         impl From<Box<[$char_t]>> for $String<'static> {
             fn from(s: Box<[$char_t]>) -> $String<'static> {
                 assert!(s.len() < (u32::MAX as usize));
-
                 if s.is_empty() {
                     return $String::new();
                 }
 
                 // SAFETY NOTE: This method produces an F_OWNED ns[C]String from
                 // a Box<[$char_t]>. this is only safe because in the Gecko
                 // tree, we use the same allocator for Rust code as for C++
                 // code, meaning that our box can be legally freed with
@@ -507,77 +590,156 @@ macro_rules! define_string_types {
             }
         }
 
         impl<'a, 'b> cmp::PartialEq<&'b str> for $FixedString<'a> {
             fn eq(&self, other: &&'b str) -> bool {
                 $AString::eq(self, *other)
             }
         }
+
+        /// An adapter type to allow for passing both types which coerce to
+        /// &[$char_type], and &$AString to a function, while still performing
+        /// optimized operations when passed the $AString.
+        pub enum $StringAdapter<'a> {
+            Borrowed($String<'a>),
+            Abstract(&'a $AString),
+        }
+
+        impl<'a> $StringAdapter<'a> {
+            fn as_ptr(&self) -> *const $AString {
+                &**self
+            }
+        }
+
+        impl<'a> Deref for $StringAdapter<'a> {
+            type Target = $AString;
+
+            fn deref(&self) -> &$AString {
+                match *self {
+                    $StringAdapter::Borrowed(ref s) => s,
+                    $StringAdapter::Abstract(ref s) => s,
+                }
+            }
+        }
+
+        /// This trait is implemented on types which are `ns[C]String`-like, in
+        /// that they can at very low cost be converted to a borrowed
+        /// `&nsA[C]String`. Unfortunately, the intermediate type
+        /// `ns[C]StringAdapter` is required as well due to types like `&[u8]`
+        /// needing to be (cheaply) wrapped in a `nsCString` on the stack to
+        /// create the `&nsACString`.
+        ///
+        /// This trait is used to DWIM when calling the methods on
+        /// `nsA[C]String`.
+        pub trait $StringLike {
+            fn adapt(&self) -> $StringAdapter;
+        }
+
+        impl<'a, T: $StringLike + ?Sized> $StringLike for &'a T {
+            fn adapt(&self) -> $StringAdapter {
+                <T as $StringLike>::adapt(*self)
+            }
+        }
+
+        impl<'a, T> $StringLike for borrow::Cow<'a, T>
+            where T: $StringLike + borrow::ToOwned + ?Sized {
+            fn adapt(&self) -> $StringAdapter {
+                <T as $StringLike>::adapt(self.as_ref())
+            }
+        }
+
+        impl $StringLike for $AString {
+            fn adapt(&self) -> $StringAdapter {
+                $StringAdapter::Abstract(self)
+            }
+        }
+
+        impl<'a> $StringLike for $String<'a> {
+            fn adapt(&self) -> $StringAdapter {
+                $StringAdapter::Abstract(self)
+            }
+        }
+
+        impl<'a> $StringLike for $FixedString<'a> {
+            fn adapt(&self) -> $StringAdapter {
+                $StringAdapter::Abstract(self)
+            }
+        }
+
+        impl $StringLike for [$char_t] {
+            fn adapt(&self) -> $StringAdapter {
+                $StringAdapter::Borrowed($String::from(self))
+            }
+        }
+
+        impl $StringLike for Vec<$char_t> {
+            fn adapt(&self) -> $StringAdapter {
+                $StringAdapter::Borrowed($String::from(&self[..]))
+            }
+        }
+
+        impl $StringLike for Box<[$char_t]> {
+            fn adapt(&self) -> $StringAdapter {
+                $StringAdapter::Borrowed($String::from(&self[..]))
+            }
+        }
     }
 }
 
 ///////////////////////////////////////////
 // Bindings for nsCString (u8 char type) //
 ///////////////////////////////////////////
 
 define_string_types! {
     char_t = u8;
 
     AString = nsACString;
     String = nsCString;
     FixedString = nsFixedCString;
 
+    StringLike = nsCStringLike;
+    StringAdapter = nsCStringAdapter;
+
     StringRepr = nsCStringRepr;
+
+    drop = Gecko_FinalizeCString;
+    assign = Gecko_AssignCString, Gecko_FallibleAssignCString;
+    append = Gecko_AppendCString, Gecko_FallibleAppendCString;
+    set_length = Gecko_SetLengthCString, Gecko_FallibleSetLengthCString;
 }
 
 impl nsACString {
-    pub fn assign<T: AsRef<[u8]> + ?Sized>(&mut self, other: &T) {
-        let s = nsCString::from(other.as_ref());
+    pub fn assign_utf16<T: nsStringLike + ?Sized>(&mut self, other: &T) {
+        self.truncate();
+        self.append_utf16(other);
+    }
+
+    pub fn fallible_assign_utf16<T: nsStringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+        self.truncate();
+        self.fallible_append_utf16(other)
+    }
+
+    pub fn append_utf16<T: nsStringLike + ?Sized>(&mut self, other: &T) {
         unsafe {
-            Gecko_AssignCString(self, &*s);
+            Gecko_AppendUTF16toCString(self, other.adapt().as_ptr());
         }
     }
 
-    pub fn assign_utf16<T: AsRef<[u16]> + ?Sized>(&mut self, other: &T) {
-        self.assign(&[]);
-        self.append_utf16(other);
-    }
-
-    pub fn append<T: AsRef<[u8]> + ?Sized>(&mut self, other: &T) {
-        let s = nsCString::from(other.as_ref());
-        unsafe {
-            Gecko_AppendCString(self, &*s);
-        }
-    }
-
-    pub fn append_utf16<T: AsRef<[u16]> + ?Sized>(&mut self, other: &T) {
-        let s = nsString::from(other.as_ref());
-        unsafe {
-            Gecko_AppendUTF16toCString(self, &*s);
+    pub fn fallible_append_utf16<T: nsStringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+        if unsafe { Gecko_FallibleAppendUTF16toCString(self, other.adapt().as_ptr()) } {
+            Ok(())
+        } else {
+            Err(())
         }
     }
 
     pub unsafe fn as_str_unchecked(&self) -> &str {
         str::from_utf8_unchecked(self)
     }
-
-    pub fn truncate(&mut self) {
-        unsafe {
-            Gecko_TruncateCString(self);
-        }
-    }
-}
-
-impl<'a> Drop for nsCString<'a> {
-    fn drop(&mut self) {
-        unsafe {
-            Gecko_FinalizeCString(&mut **self);
-        }
-    }
 }
 
 impl<'a> From<&'a str> for nsCString<'a> {
     fn from(s: &'a str) -> nsCString<'a> {
         s.as_bytes().into()
     }
 }
 
@@ -614,16 +776,34 @@ impl fmt::Debug for nsACString {
 }
 
 impl cmp::PartialEq<str> for nsACString {
     fn eq(&self, other: &str) -> bool {
         &self[..] == other.as_bytes()
     }
 }
 
+impl nsCStringLike for str {
+    fn adapt(&self) -> nsCStringAdapter {
+        nsCStringAdapter::Borrowed(nsCString::from(self))
+    }
+}
+
+impl nsCStringLike for String {
+    fn adapt(&self) -> nsCStringAdapter {
+        nsCStringAdapter::Borrowed(nsCString::from(&self[..]))
+    }
+}
+
+impl nsCStringLike for Box<str> {
+    fn adapt(&self) -> nsCStringAdapter {
+        nsCStringAdapter::Borrowed(nsCString::from(&self[..]))
+    }
+}
+
 #[macro_export]
 macro_rules! ns_auto_cstring {
     ($name:ident) => {
         let mut buf: [u8; 64] = [0; 64];
         let mut $name = $crate::nsFixedCString::new(&mut buf);
     }
 }
 
@@ -633,57 +813,49 @@ macro_rules! ns_auto_cstring {
 
 define_string_types! {
     char_t = u16;
 
     AString = nsAString;
     String = nsString;
     FixedString = nsFixedString;
 
+    StringLike = nsStringLike;
+    StringAdapter = nsStringAdapter;
+
     StringRepr = nsStringRepr;
+
+    drop = Gecko_FinalizeString;
+    assign = Gecko_AssignString, Gecko_FallibleAssignString;
+    append = Gecko_AppendString, Gecko_FallibleAppendString;
+    set_length = Gecko_SetLengthString, Gecko_FallibleSetLengthString;
 }
 
 impl nsAString {
-    pub fn assign<T: AsRef<[u16]> + ?Sized>(&mut self, other: &T) {
-        let s = nsString::from(other.as_ref());
-        unsafe {
-            Gecko_AssignString(self, &*s);
-        }
-    }
-
-    pub fn assign_utf8<T: AsRef<[u8]> + ?Sized>(&mut self, other: &T) {
-        self.assign(&[]);
+    pub fn assign_utf8<T: nsCStringLike + ?Sized>(&mut self, other: &T) {
+        self.truncate();
         self.append_utf8(other);
     }
 
-    pub fn append<T: AsRef<[u16]> + ?Sized>(&mut self, other: &T) {
-        let s = nsString::from(other.as_ref());
+    pub fn fallible_assign_utf8<T: nsCStringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+        self.truncate();
+        self.fallible_append_utf8(other)
+    }
+
+    pub fn append_utf8<T: nsCStringLike + ?Sized>(&mut self, other: &T) {
         unsafe {
-            Gecko_AppendString(self, &*s);
+            Gecko_AppendUTF8toString(self, other.adapt().as_ptr());
         }
     }
 
-    pub fn append_utf8<T: AsRef<[u8]> + ?Sized>(&mut self, other: &T) {
-        let s = nsCString::from(other.as_ref());
-        unsafe {
-            Gecko_AppendUTF8toString(self, &*s);
-        }
-    }
-
-    pub fn truncate(&mut self) {
-        unsafe {
-            Gecko_TruncateString(self);
-        }
-    }
-}
-
-impl<'a> Drop for nsString<'a> {
-    fn drop(&mut self) {
-        unsafe {
-            Gecko_FinalizeString(&mut **self);
+    pub fn fallible_append_utf8<T: nsCStringLike + ?Sized>(&mut self, other: &T) -> Result<(), ()> {
+        if unsafe { Gecko_FallibleAppendUTF8toString(self, other.adapt().as_ptr()) } {
+            Ok(())
+        } else {
+            Err(())
         }
     }
 }
 
 // NOTE: The From impl for a string slice for nsString produces a <'static>
 // lifetime, as it allocates.
 impl<'a> From<&'a str> for nsString<'static> {
     fn from(s: &'a str) -> nsString<'static> {
@@ -726,29 +898,37 @@ macro_rules! ns_auto_string {
         let mut $name = $crate::nsFixedString::new(&mut buf);
     }
 }
 
 #[cfg(not(debug_assertions))]
 #[allow(non_snake_case)]
 unsafe fn Gecko_IncrementStringAdoptCount(_: *mut c_void) {}
 
-// NOTE: These bindings currently only expose infallible operations. Perhaps
-// consider allowing for fallible methods?
 extern "C" {
     #[cfg(debug_assertions)]
     fn Gecko_IncrementStringAdoptCount(data: *mut c_void);
 
     // Gecko implementation in nsSubstring.cpp
     fn Gecko_FinalizeCString(this: *mut nsACString);
+
     fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString);
     fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString);
-    fn Gecko_TruncateCString(this: *mut nsACString);
+    fn Gecko_SetLengthCString(this: *mut nsACString, length: u32);
+    fn Gecko_FallibleAssignCString(this: *mut nsACString, other: *const nsACString) -> bool;
+    fn Gecko_FallibleAppendCString(this: *mut nsACString, other: *const nsACString) -> bool;
+    fn Gecko_FallibleSetLengthCString(this: *mut nsACString, length: u32) -> bool;
 
     fn Gecko_FinalizeString(this: *mut nsAString);
+
     fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString);
     fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString);
-    fn Gecko_TruncateString(this: *mut nsAString);
+    fn Gecko_SetLengthString(this: *mut nsAString, length: u32);
+    fn Gecko_FallibleAssignString(this: *mut nsAString, other: *const nsAString) -> bool;
+    fn Gecko_FallibleAppendString(this: *mut nsAString, other: *const nsAString) -> bool;
+    fn Gecko_FallibleSetLengthString(this: *mut nsAString, length: u32) -> bool;
 
     // Gecko implementation in nsReadableUtils.cpp
     fn Gecko_AppendUTF16toCString(this: *mut nsACString, other: *const nsAString);
     fn Gecko_AppendUTF8toString(this: *mut nsAString, other: *const nsACString);
+    fn Gecko_FallibleAppendUTF16toCString(this: *mut nsACString, other: *const nsAString) -> bool;
+    fn Gecko_FallibleAppendUTF8toString(this: *mut nsAString, other: *const nsACString) -> bool;
 }
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -190,17 +190,26 @@ pub fn parse_integer(input: &mut Parser)
         }
         _ => Err(())
     }
 }
 
 #[allow(missing_docs)]
 pub fn parse_number(input: &mut Parser) -> Result<f32, ()> {
     match try!(input.next()) {
-        Token::Number(ref value) => Ok(value.value),
+        Token::Number(ref value) => {
+            use std::f32;
+            if value.value.is_finite() {
+                Ok(value.value)
+            } else if value.value.is_sign_positive() {
+                Ok(f32::MAX)
+            } else {
+                Ok(f32::MIN)
+            }
+        },
         Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
             let ast = try!(input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(i, CalcUnit::Number)));
 
             let mut result = None;
 
             for ref node in ast.products {
                 match try!(CalcLengthOrPercentage::simplify_product(node)) {
                     SimplifiedValueNode::Number(val) =>
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -43,25 +43,26 @@ use style::gecko_bindings::bindings::Gec
 use style::gecko_bindings::bindings::RawGeckoComputedKeyframeValuesListBorrowedMut;
 use style::gecko_bindings::bindings::RawGeckoElementBorrowed;
 use style::gecko_bindings::bindings::RawServoAnimationValueBorrowed;
 use style::gecko_bindings::bindings::RawServoAnimationValueMapBorrowed;
 use style::gecko_bindings::bindings::RawServoAnimationValueStrong;
 use style::gecko_bindings::bindings::RawServoImportRuleBorrowed;
 use style::gecko_bindings::bindings::ServoComputedValuesBorrowedOrNull;
 use style::gecko_bindings::bindings::nsTArrayBorrowed_uintptr_t;
+use style::gecko_bindings::bindings::nsTimingFunctionBorrowed;
+use style::gecko_bindings::bindings::nsTimingFunctionBorrowedMut;
 use style::gecko_bindings::structs;
 use style::gecko_bindings::structs::{SheetParsingMode, nsIAtom, nsCSSPropertyID};
 use style::gecko_bindings::structs::{ThreadSafePrincipalHolder, ThreadSafeURIHolder};
 use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint};
 use style::gecko_bindings::structs::Loader;
 use style::gecko_bindings::structs::RawGeckoPresContextOwned;
 use style::gecko_bindings::structs::ServoStyleSheet;
 use style::gecko_bindings::structs::nsCSSValueSharedList;
-use style::gecko_bindings::structs::nsTimingFunction;
 use style::gecko_bindings::structs::nsresult;
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI};
 use style::gecko_properties::{self, style_structs};
 use style::keyframes::KeyframesStepValue;
 use style::media_queries::{MediaList, parse_media_query_list};
 use style::parallel;
@@ -786,16 +787,37 @@ pub extern "C" fn Servo_ParseProperty(pr
             parsed.expand(|d| block.push(d, Importance::Normal));
             Arc::new(global_style_data.shared_lock.wrap(block)).into_strong()
         }
         Err(_) => RawServoDeclarationBlockStrong::null()
     }
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_ParseEasing(easing: *const nsAString,
+                                    base: *const nsACString,
+                                    data: *const structs::GeckoParserExtraData,
+                                    output: nsTimingFunctionBorrowedMut)
+                                    -> bool {
+    use style::properties::longhands::transition_timing_function;
+
+    make_context!((base, data) => (base_url, extra_data));
+    let reporter = StdoutErrorReporter;
+    let context = ParserContext::new_with_extra_data(Origin::Author, &base_url, &reporter, extra_data);
+    let easing = unsafe { (*easing).to_string() };
+    match transition_timing_function::single_value::parse(&context, &mut Parser::new(&easing)) {
+        Ok(parsed_easing) => {
+            *output = parsed_easing.into();
+            true
+        },
+        Err(_) => false
+    }
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_ParseStyleAttribute(data: *const nsACString,
                                             base: *const nsACString,
                                             raw_extra_data: *const structs::GeckoParserExtraData)
                                             -> RawServoDeclarationBlockStrong {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let value = unsafe { data.as_ref().unwrap().as_str_unchecked() };
     make_context!((base, raw_extra_data) => (base_url, extra_data));
     Arc::new(global_style_data.shared_lock.wrap(
@@ -1612,43 +1634,42 @@ pub extern "C" fn Servo_AssertTreeIsClea
     }
 
     assert_subtree_is_clean(root);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_FillKeyframesForName(raw_data: RawServoStyleSetBorrowed,
                                                       name: *const nsACString,
-                                                      timing_function: *const nsTimingFunction,
+                                                      timing_function: nsTimingFunctionBorrowed,
                                                       style: ServoComputedValuesBorrowed,
                                                       keyframes: RawGeckoKeyframeListBorrowedMut) -> bool {
     use style::gecko_bindings::structs::Keyframe;
     use style::properties::LonghandIdSet;
 
 
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let name = unsafe { Atom::from(name.as_ref().unwrap().as_str_unchecked()) };
-    let style_timing_function = unsafe { timing_function.as_ref().unwrap() };
     let style = ComputedValues::as_arc(&style);
 
     if let Some(ref animation) = data.stylist.animations().get(&name) {
        let global_style_data = &*GLOBAL_STYLE_DATA;
        let guard = global_style_data.shared_lock.read();
        for step in &animation.steps {
           // Override timing_function if the keyframe has animation-timing-function.
           let timing_function = if let Some(val) = step.get_animation_timing_function(&guard) {
               val.into()
           } else {
-              *style_timing_function
+              *timing_function
           };
 
           let keyframe = unsafe {
-                Gecko_AnimationAppendKeyframe(keyframes,
-                                              step.start_percentage.0 as f32,
-                                              &timing_function)
+              Gecko_AnimationAppendKeyframe(keyframes,
+                                            step.start_percentage.0 as f32,
+                                            &timing_function)
           };
 
           fn add_computed_property_value(keyframe: *mut Keyframe,
                                          index: usize,
                                          style: &ComputedValues,
                                          property: &TransitionProperty,
                                          shared_lock: &SharedRwLock) {
               let block = style.to_declaration_block(property.clone().into());
--- a/testing/mochitest/ShutdownLeaksCollector.jsm
+++ b/testing/mochitest/ShutdownLeaksCollector.jsm
@@ -2,16 +2,17 @@
  * 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/. */
 
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
 
 this.EXPORTED_SYMBOLS = ["ContentCollector"];
 
 // This listens for the message "browser-test:collect-request". When it gets it,
 // it runs some GCs and CCs, then prints out a message indicating the collections
 // are complete. Mochitest uses this information to determine when windows and
 // docshells should be destroyed.
 
@@ -31,24 +32,41 @@ var ContentCollector = {
   receiveMessage: function(aMessage) {
     switch (aMessage.name) {
       case "browser-test:collect-request":
         Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
 
         Cu.forceGC();
         Cu.forceCC();
 
-        Cu.schedulePreciseShrinkingGC(() => {
-          Cu.forceCC();
+        let shutdownCleanup = aCallback => {
+          Cu.schedulePreciseShrinkingGC(() => {
+            // Run the GC and CC a few times to make sure that as much
+            // as possible is freed.
+            Cu.forceGC();
+            Cu.forceCC();
+            aCallback();
+          });
+        };
 
-          let pid = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processID;
-          dump("Completed ShutdownLeaks collections in process " + pid + "\n")});
-
-          let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
-                       .getService(Ci.nsISyncMessageSender);
-          cpmm.removeMessageListener("browser-test:collect-request", this);
+        shutdownCleanup(() => {
+          setTimeout(() => {
+            shutdownCleanup(() => {
+              this.finish();
+            })
+          }, 1000);
+        });
 
         break;
     }
-  }
+  },
+
+  finish() {
+    let pid = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processID;
+    dump("Completed ShutdownLeaks collections in process " + pid + "\n");
+
+    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+                 .getService(Ci.nsISyncMessageSender);
+    cpmm.removeMessageListener("browser-test:collect-request", this);
+  },
 
 };
 ContentCollector.init();
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -427,21 +427,16 @@ class RobocopCommands(MachCommandBase):
     def run_robocop(self, serve=False, **kwargs):
         if serve:
             kwargs['autorun'] = False
 
         if not kwargs.get('robocopIni'):
             kwargs['robocopIni'] = os.path.join(self.topobjdir, '_tests', 'testing',
                                                 'mochitest', 'robocop.ini')
 
-        if not kwargs.get('robocopApk'):
-            kwargs['robocopApk'] = os.path.join(self.topobjdir, 'mobile', 'android',
-                                                'tests', 'browser', 'robocop',
-                                                'robocop-debug.apk')
-
         from mozbuild.controller.building import BuildDriver
         self._ensure_state_subdir_exists('.')
 
         test_paths = kwargs['test_paths']
         kwargs['test_paths'] = []
 
         from mozbuild.testing import TestResolver
         resolver = self._spawn(TestResolver)
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -1021,19 +1021,25 @@ class AndroidArguments(ArgumentContainer
         if options.robocopIni != "":
             if not os.path.exists(options.robocopIni):
                 parser.error(
                     "Unable to find specified robocop .ini manifest '%s'" %
                     options.robocopIni)
             options.robocopIni = os.path.abspath(options.robocopIni)
 
             if not options.robocopApk and build_obj:
-                options.robocopApk = os.path.join(build_obj.topobjdir, 'mobile', 'android',
-                                                  'tests', 'browser',
-                                                  'robocop', 'robocop-debug.apk')
+                if build_obj.substs.get('MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE'):
+                    options.robocopApk = os.path.join(build_obj.topobjdir, 'gradle', 'build',
+                                                      'mobile', 'android', 'app', 'outputs', 'apk',
+                                                      'app-automation-debug-androidTest-'
+                                                      'unaligned.apk')
+                else:
+                    options.robocopApk = os.path.join(build_obj.topobjdir, 'mobile', 'android',
+                                                      'tests', 'browser',
+                                                      'robocop', 'robocop-debug.apk')
 
         if options.robocopApk != "":
             if not os.path.exists(options.robocopApk):
                 parser.error(
                     "Unable to find robocop APK '%s'" %
                     options.robocopApk)
             options.robocopApk = os.path.abspath(options.robocopApk)
 
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -1386,17 +1386,17 @@ var getAnno = Task.async(function* (guid
 });
 
 var tagItem = Task.async(function(item, tags) {
   if (!item.url) {
     return [];
   }
 
   // Remove leading and trailing whitespace, then filter out empty tags.
-  let newTags = tags.map(tag => tag.trim()).filter(Boolean);
+  let newTags = tags ? tags.map(tag => tag.trim()).filter(Boolean) : [];
 
   // Removing the last tagged item will also remove the tag. To preserve
   // tag IDs, we temporarily tag a dummy URI, ensuring the tags exist.
   let dummyURI = PlacesUtils.toURI("about:weave#BStore_tagURI");
   let bookmarkURI = PlacesUtils.toURI(item.url.href);
   PlacesUtils.tagging.tagURI(dummyURI, newTags, SOURCE_SYNC);
   PlacesUtils.tagging.untagURI(bookmarkURI, null, SOURCE_SYNC);
   PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -1564,16 +1564,30 @@ Search.prototype = {
   *_matchUnknownUrl() {
     let flags = Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
                 Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
     let fixupInfo = null;
     try {
       fixupInfo = Services.uriFixup.getFixupURIInfo(this._originalSearchString,
                                                     flags);
     } catch (e) {
+      if (e.result == Cr.NS_ERROR_MALFORMED_URI && !Prefs.keywordEnabled) {
+        let value = PlacesUtils.mozActionURI("visiturl", {
+          url: this._originalSearchString,
+          input: this._originalSearchString,
+        });
+        this._addMatch({
+          value,
+          comment: this._originalSearchString,
+          style: "action visiturl",
+          frecency: 0,
+        });
+
+        return true;
+      }
       return false;
     }
 
     // If the URI cannot be fixed or the preferred URI would do a keyword search,
     // that basically means this isn't useful to us. Note that
     // fixupInfo.keywordAsSent will never be true if the keyword.enabled pref
     // is false or there are no engines, so in that case we will always return
     // a "visit".
--- a/toolkit/components/places/tests/unifiedcomplete/test_visit_url.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_visit_url.js
@@ -136,16 +136,22 @@ add_task(function*() {
     Services.prefs.clearUserPref("keyword.enabled");
   });
   do_print("visit url, keyword.enabled = false");
   yield check_autocomplete({
     search: "bacon",
     searchParam: "enable-actions",
     matches: [ makeVisitMatch("bacon", "http://bacon/", { heuristic: true }) ]
   });
+  do_print("visit two word query, keyword.enabled = false");
+  yield check_autocomplete({
+    search: "bacon lovers",
+    searchParam: "enable-actions",
+    matches: [ makeVisitMatch("bacon lovers", "bacon lovers", { heuristic: true }) ]
+  });
   Services.prefs.setBoolPref("keyword.enabled", keywordEnabled);
 
   do_print("visit url, scheme+host");
   yield check_autocomplete({
     search: "http://example",
     searchParam: "enable-actions",
     matches: [ makeVisitMatch("http://example", "http://example/", { heuristic: true }) ]
   });
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -9678,17 +9678,17 @@
     "description": "Sanitize: Time it takes to sanitize site-specific settings (ms)"
   },
   "FX_SANITIZE_OPENWINDOWS": {
     "alert_emails": ["firefox-dev@mozilla.org"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 20,
-    "description": "Sanitize: Time it takes to sanitize the open windows list (ms)"
+    "description": "Sanitize: Time it takes to sanitize the open windows list (ms). On Android, this is the time it takes to close all open tabs (ms)."
   },
   "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"
   },
--- a/toolkit/themes/linux/global/autocomplete.css
+++ b/toolkit/themes/linux/global/autocomplete.css
@@ -100,16 +100,17 @@ treechildren.autocomplete-treebody::-moz
 
 .autocomplete-richlistbox {
   -moz-appearance: none;
   margin: 1px;
   background-color: transparent;
 }
 
 .autocomplete-richlistitem[selected] {
+  background-color: Highlight;
   color: HighlightText;
 }
 
 .ac-type-icon {
   width: 16px;
   height: 16px;
   max-width: 16px;
   max-height: 16px;
--- a/toolkit/themes/linux/global/button.css
+++ b/toolkit/themes/linux/global/button.css
@@ -7,17 +7,17 @@
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* :::::::::: button :::::::::: */
 
 button {
   -moz-appearance: button;
-  margin: 1px 5px 2px 5px;
+  margin: 1px 5px 2px;
   min-width: 6.3em;
   color: ButtonText;
   text-shadow: none;
 }
 
 .button-box {
   -moz-appearance: button-focus;
 }
@@ -28,32 +28,23 @@ button {
 
 .button-text {
   margin: 0 !important;
   text-align: center;
 }
 
 /* .......... hover state .......... */
 
-button:hover {
+button:hover:not(:-moz-any(:active,[disabled="true"],[open="true"],[checked="true"])) {
   color: -moz-buttonhovertext;
 }
 
-/* .......... active/open/checked state .......... */
-
-button:hover:active,
-button[open="true"],
-button[checked="true"] {
-  color: ButtonText;
-}
-
 /* .......... disabled state .......... */
 
-button[disabled="true"],
-button[disabled="true"]:hover:active {
+button[disabled="true"] {
   color: GrayText;
 }
 
 /* ::::: menu/menu-button buttons ::::: */
 
 button[type="menu-button"] {
   -moz-appearance: dualbutton;
 }
--- a/toolkit/themes/windows/global/autocomplete.css
+++ b/toolkit/themes/windows/global/autocomplete.css
@@ -92,16 +92,21 @@ treechildren.autocomplete-treebody::-moz
 
 /* ::::: richlistbox autocomplete ::::: */
 
 .autocomplete-richlistbox {
   -moz-appearance: none;
   margin: 0;
 }
 
+.autocomplete-richlistitem[selected] {
+  background-color: Highlight;
+  color: HighlightText;
+}
+
 .ac-type-icon {
   width: 16px;
   height: 16px;
   max-width: 16px;
   max-height: 16px;
   margin-inline-start: 14px;
   margin-inline-end: 6px;
 }