Merge autoland to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Sat, 28 Apr 2018 00:52:20 +0300
changeset 472122 2a66df1058cd34a1d48e1e1e3bcd2db36714ed51
parent 472104 a90bf0a31015017868527c8f11a79a1cdc83c537 (current diff)
parent 472121 9c70604962648780c82ae1a4773b0f249b26863f (diff)
child 472144 8b2c1fc3d6c348f053fe33a478fa3b1ddb5eb8a6
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.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 mozilla-central. a=merge
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2830,16 +2830,17 @@ function losslessDecodeURI(aURI) {
                            encodeURIComponent);
       } catch (e) {}
     }
   }
 
   // Encode invisible characters (C0/C1 control characters, U+007F [DEL],
   // U+00A0 [no-break space], line and paragraph separator,
   // object replacement character) (bug 452979, bug 909264)
+  // eslint-disable-next-line no-control-regex
   value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g,
                         encodeURIComponent);
 
   // Encode default ignorable characters (bug 546013)
   // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186).
   // This includes all bidirectional formatting characters.
   // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
   value = value.replace(/[\u00ad\u034f\u061c\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
--- a/browser/base/content/test/general/browser_contentSearchUI.js
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -4,16 +4,21 @@
 const TEST_PAGE_BASENAME = "contentSearchUI.html";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js";
 const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
 
 const TEST_MSG = "ContentSearchUIControllerTest";
 
+let {SearchTestUtils} = ChromeUtils.import(
+  "resource://testing-common/SearchTestUtils.jsm", {});
+
+SearchTestUtils.init(Assert, registerCleanupFunction);
+
 requestLongerTimeout(2);
 
 add_task(async function emptyInput() {
   await setUp();
 
   let state = await msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
@@ -386,16 +391,17 @@ add_task(async function cycleEngines() {
   await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
 
   let promiseEngineChange = function(newEngineName) {
     return new Promise(resolve => {
       Services.obs.addObserver(function resolver(subj, topic, data) {
         if (data != "engine-current") {
           return;
         }
+        subj.QueryInterface(Ci.nsISearchEngine);
         SimpleTest.is(subj.name, newEngineName, "Engine cycled correctly");
         Services.obs.removeObserver(resolver, "browser-search-engine-modified");
         resolve();
       }, "browser-search-engine-modified");
     });
   };
 
   let p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME);
@@ -734,18 +740,21 @@ function promiseMsg(name, type, msgMan) 
 }
 
 function setUpEngines() {
   return (async function() {
     info("Removing default search engines");
     let currentEngineName = Services.search.currentEngine.name;
     let currentEngines = Services.search.getVisibleEngines();
     info("Adding test search engines");
-    let engine1 = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
-    await promiseNewSearchEngine(TEST_ENGINE_2_BASENAME);
+    let rootDir = getRootDirectory(gTestPath);
+    let engine1 = await SearchTestUtils.promiseNewSearchEngine(
+      rootDir + TEST_ENGINE_BASENAME);
+    await SearchTestUtils.promiseNewSearchEngine(
+      rootDir + TEST_ENGINE_2_BASENAME);
     Services.search.currentEngine = engine1;
     for (let engine of currentEngines) {
       Services.search.removeEngine(engine);
     }
     registerCleanupFunction(() => {
       Services.search.restoreDefaultEngines();
       Services.search.currentEngine = Services.search.getEngineByName(currentEngineName);
     });
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -521,34 +521,16 @@ function promiseNotificationShown(notifi
   if (win.PopupNotifications.panel.state == "open") {
     return Promise.resolve();
   }
   let panelPromise = promisePopupShown(win.PopupNotifications.panel);
   notification.reshow();
   return panelPromise;
 }
 
-function promiseNewSearchEngine(basename) {
-  return new Promise((resolve, reject) => {
-    info("Waiting for engine to be added: " + basename);
-    let url = getRootDirectory(gTestPath) + basename;
-    Services.search.addEngine(url, null, "", false, {
-      onSuccess(engine) {
-        info("Search engine added: " + basename);
-        registerCleanupFunction(() => Services.search.removeEngine(engine));
-        resolve(engine);
-      },
-      onError(errCode) {
-        Assert.ok(false, "addEngine failed with error code " + errCode);
-        reject();
-      },
-    });
-  });
-}
-
 /**
  * Resolves when a bookmark with the given uri is added.
  */
 function promiseOnBookmarkItemAdded(aExpectedURI) {
   return new Promise((resolve, reject) => {
     let bookmarksObserver = {
       onItemAdded(aItemId, aFolderId, aIndex, aItemType, aURI) {
         info("Added a bookmark to " + aURI.spec);
--- a/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
+++ b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
@@ -14,17 +14,18 @@ add_task(async function switchToTab() {
   is(result.label, "about:about about:about Tab", "Result a11y label should be: <title> <url> Tab");
 
   gURLBar.popup.hidePopup();
   await promisePopupHidden(gURLBar.popup);
   gBrowser.removeTab(tab);
 });
 
 add_task(async function searchSuggestions() {
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
   Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
   registerCleanupFunction(function() {
     Services.search.currentEngine = oldCurrentEngine;
     Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -3,17 +3,18 @@ const TEST_ENGINE_BASENAME = "searchSugg
 let gMaxResults;
 
 add_task(async function init() {
   Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
   gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
 
   // Add a search suggestion engine and move it to the front so that it appears
   // as the first one-off.
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   Services.search.moveEngine(engine, 0);
 
   registerCleanupFunction(async function() {
     await hidePopup();
     await PlacesUtils.history.clear();
   });
 
   await PlacesUtils.history.clear();
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs_searchSuggestions.js
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs_searchSuggestions.js
@@ -1,17 +1,18 @@
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
 add_task(async function init() {
   await PlacesUtils.history.clear();
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.oneOffSearches", true],
           ["browser.urlbar.suggest.searches", true]],
   });
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.moveEngine(engine, 0);
   Services.search.currentEngine = engine;
   registerCleanupFunction(async function() {
     Services.search.currentEngine = oldCurrentEngine;
 
     await PlacesUtils.history.clear();
     // Make sure the popup is closed for the next test.
--- a/browser/base/content/test/urlbar/browser_urlbarPlaceholder.js
+++ b/browser/base/content/test/urlbar/browser_urlbarPlaceholder.js
@@ -12,17 +12,18 @@ const TEST_ENGINE_BASENAME = "searchSugg
 
 const originalEngine = Services.search.currentEngine;
 const expectedString = gBrowserBundle.formatStringFromName("urlbar.placeholder",
   [originalEngine.name], 1);
 var extraEngine;
 var tabs = [];
 
 add_task(async function setup() {
-  extraEngine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let rootDir = getRootDirectory(gTestPath);
+  extraEngine = await SearchTestUtils.promiseNewSearchEngine(rootDir + TEST_ENGINE_BASENAME);
 
   // Force display of a tab with a URL bar, to clear out any possible placeholder
   // initialization listeners that happen on startup.
   let urlTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
   BrowserTestUtils.removeTab(urlTab);
 
   registerCleanupFunction(() => {
     Services.search.currentEngine = originalEngine;
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
@@ -1,16 +1,17 @@
 const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
 // Must run first.
 add_task(async function prepare() {
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
     Services.search.currentEngine = oldCurrentEngine;
 
     // Clicking suggestions causes visits to search results pages, so clear that
     // history now.
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions_opt-out.js
@@ -4,17 +4,18 @@
 const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
 const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
 const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
 const TIMES_PREF = "browser.urlbar.timesBeforeHidingSuggestionsHint";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 const ONEOFF_PREF = "browser.urlbar.oneOffSearches";
 
 add_task(async function prepare() {
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   let defaults = Services.prefs.getDefaultBranch("browser.urlbar.");
   let searchSuggestionsDefault = defaults.getBoolPref("suggest.searches");
   defaults.setBoolPref("suggest.searches", true);
   let suggestionsChoice = Services.prefs.getBoolPref(CHOICE_PREF);
   Services.prefs.setBoolPref(CHOICE_PREF, false);
--- a/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
@@ -4,17 +4,18 @@ ChromeUtils.import("resource:///modules/
 
 const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
 // Must run first.
 add_task(async function prepare() {
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
 
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
     Services.search.currentEngine = oldCurrentEngine;
 
     // Clicking urlbar results causes visits to their associated pages, so clear
--- a/browser/base/content/test/urlbar/browser_urlbarStopSearchOnSelection.js
+++ b/browser/base/content/test/urlbar/browser_urlbarStopSearchOnSelection.js
@@ -14,17 +14,18 @@ const TEST_ENGINE_SUGGESTIONS_TIMEOUT = 
 const TEST_ENGINE_NUM_EXPECTED_RESULTS = 2;
 
 add_task(async function init() {
   await PlacesUtils.history.clear();
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.suggest.searches", true]],
   });
   // Add a test search engine that returns suggestions on a delay.
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.moveEngine(engine, 0);
   Services.search.currentEngine = engine;
   registerCleanupFunction(async () => {
     Services.search.currentEngine = oldCurrentEngine;
     await PlacesUtils.history.clear();
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
--- a/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
@@ -24,17 +24,18 @@ add_task(async function setup() {
           ["browser.urlbar.speculativeConnect.enabled", true],
           // In mochitest this number is 0 by default but we have to turn it on.
           ["network.http.speculative-parallel-limit", 6],
           // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
           // networking problem.
           ["network.dns.disableIPv6", true]],
   });
 
-  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldCurrentEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
 
   registerCleanupFunction(async function() {
     await PlacesUtils.history.clear();
     Services.search.currentEngine = oldCurrentEngine;
     gHttpServer.identity.remove(gScheme, gHost, gPort);
     gHttpServer.stop(() => {
@@ -48,9 +49,8 @@ add_task(async function connect_search_e
   await promiseAutocompleteResultPopup("foo", window, true);
   // Check if the first result is with type "searchengine"
   let controller = gURLBar.popup.input.controller;
   let style = controller.getStyleAt(0);
   is(style.includes("searchengine"), true, "The first result type is searchengine");
   await promiseSpeculativeConnection(gHttpServer);
   is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
 });
-
--- a/browser/base/content/test/urlbar/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -5,16 +5,20 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.defineModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Preferences",
   "resource://gre/modules/Preferences.jsm");
 ChromeUtils.defineModuleGetter(this, "HttpServer",
   "resource://testing-common/httpd.js");
+ChromeUtils.defineModuleGetter(this, "SearchTestUtils",
+  "resource://testing-common/SearchTestUtils.jsm");
+
+SearchTestUtils.init(Assert, registerCleanupFunction);
 
 /**
  * Waits for the next top-level document load in the current browser.  The URI
  * of the document is compared against aExpectedURL.  The load is then stopped
  * before it actually starts.
  *
  * @param aExpectedURL
  *        The URL of the document that is expected to load.
@@ -199,34 +203,16 @@ function promiseAutocompleteResultPopup(
       win.gURLBar.dispatchEvent(event);
     }
     win.gURLBar.controller.startSearch(inputText);
   }, win);
 
   return promiseSearchComplete(win, dontAnimate);
 }
 
-function promiseNewSearchEngine(basename) {
-  return new Promise((resolve, reject) => {
-    info("Waiting for engine to be added: " + basename);
-    let url = getRootDirectory(gTestPath) + basename;
-    Services.search.addEngine(url, null, "", false, {
-      onSuccess(engine) {
-        info("Search engine added: " + basename);
-        registerCleanupFunction(() => Services.search.removeEngine(engine));
-        resolve(engine);
-      },
-      onError(errCode) {
-        Assert.ok(false, "addEngine failed with error code " + errCode);
-        reject();
-      },
-    });
-  });
-}
-
 function promisePageActionPanelOpen() {
   let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);
   return BrowserTestUtils.waitForCondition(() => {
     // Wait for the main page action button to become visible.  It's hidden for
     // some URIs, so depending on when this is called, it may not yet be quite
     // visible.  It's up to the caller to make sure it will be visible.
     info("Waiting for main page action button to have non-0 size");
--- a/browser/components/search/moz.build
+++ b/browser/components/search/moz.build
@@ -5,12 +5,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini',
     'test/google_codes/browser.ini',
     'test/google_nocodes/browser.ini',
 ]
 
+TESTING_JS_MODULES += [
+    'test/SearchTestUtils.jsm',
+]
+
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Search')
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/SearchTestUtils.jsm
@@ -0,0 +1,43 @@
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = [
+  "SearchTestUtils",
+];
+
+var gTestGlobals;
+
+var SearchTestUtils = Object.freeze({
+  init(Assert, registerCleanupFunction) {
+    gTestGlobals = {
+      Assert,
+      registerCleanupFunction
+    };
+  },
+
+  /**
+   * Adds a search engine to the search service. It will remove the engine
+   * at the end of the test.
+   *
+   * @param {String}   url                     The URL of the engine to add.
+   * @param {Function} registerCleanupFunction Pass the registerCleanupFunction
+   *                                           from the test's scope.
+   * @returns {Promise} Returns a promise that is resolved with the new engine
+   *                    or rejected if it fails.
+   */
+  promiseNewSearchEngine(url) {
+    return new Promise((resolve, reject) => {
+      Services.search.addEngine(url, null, "", false, {
+        onSuccess(engine) {
+          gTestGlobals.registerCleanupFunction(() => Services.search.removeEngine(engine));
+          resolve(engine);
+        },
+        onError(errCode) {
+          gTestGlobals.Assert.ok(false, `addEngine failed with error code ${errCode}`);
+          reject();
+        },
+      });
+    });
+  }
+});
--- a/browser/extensions/followonsearch/content/followonsearch-fs.js
+++ b/browser/extensions/followonsearch/content/followonsearch-fs.js
@@ -87,16 +87,25 @@ let searchDomains = [{
     "www.google.com.vn", "www.google.vu", "www.google.ws", "www.google.co.za",
     "www.google.co.zm", "www.google.co.zw",
   ],
   "search": "q",
   "prefix": ["client"],
   "followOnSearch": "oq",
   "codes": ["firefox-b-ab", "firefox-b", "firefox-b-1-ab", "firefox-b-1"],
   "sap": "google",
+}, {
+  // This is intended only for tests.
+  "domains": [ "mochi.test" ],
+  "search": "m",
+  "prefix": ["mt"],
+  "followOnSearch": "mtfo",
+  "reportPrefix": "form",
+  "codes": ["TEST"],
+  "sap": "mochitest"
 }];
 
 function getSearchDomainCodes(host) {
   for (let domainInfo of searchDomains) {
     if (domainInfo.domains.includes(host)) {
       return domainInfo;
     }
   }
--- a/browser/extensions/followonsearch/moz.build
+++ b/browser/extensions/followonsearch/moz.build
@@ -7,9 +7,11 @@
 with Files("**"):
     BUG_COMPONENT = ("Firefox", "Search")
 
 FINAL_TARGET_FILES.features['followonsearch@mozilla.com'] += [
   'bootstrap.js',
   'install.rdf',
 ]
 
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
 JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/followonsearch/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test",
+  ],
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/followonsearch/test/browser/browser.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+
+[browser_followOnTelemetry.js]
+support-files =
+  test.html
+  test2.html
+  testEngine.xml
new file mode 100644
--- /dev/null
+++ b/browser/extensions/followonsearch/test/browser/browser_followOnTelemetry.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "SearchTestUtils",
+  "resource://testing-common/SearchTestUtils.jsm");
+
+SearchTestUtils.init(Assert, registerCleanupFunction);
+
+const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/followonsearch/test/browser/";
+const TEST_ENGINE_BASENAME = "testEngine.xml";
+
+add_task(async function test_followOnSearchTelemetry() {
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+    registerCleanupFunction);
+  let oldCurrentEngine = Services.search.currentEngine;
+  Services.search.currentEngine = engine;
+
+  let histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+  histogram.clear();
+
+  registerCleanupFunction(() => Services.search.currentEngine = oldCurrentEngine);
+
+  await BrowserTestUtils.withNewTab({gBrowser}, async browser => {
+    // Open the initial search page via entering a search on the URL bar.
+    let loadPromise = BrowserTestUtils.waitForLocationChange(gBrowser,
+      `${BASE_URL}test.html?searchm=test&mt=TEST`);
+
+    gURLBar.focus();
+    EventUtils.sendString("test");
+    EventUtils.sendKey("return");
+
+    await loadPromise;
+
+    // Perform a follow-on search, selecting the form in the page.
+    loadPromise = BrowserTestUtils.waitForLocationChange(gBrowser,
+      `${BASE_URL}test2.html?mtfo=followonsearchtest&mt=TEST&m=test`);
+
+    await ContentTask.spawn(browser, null, function() {
+      content.document.getElementById("submit").click();
+    });
+
+    await loadPromise;
+
+    let snapshot;
+
+    // We have to wait for the snapshot to come in, as there's async functionality
+    // in the extension.
+    await TestUtils.waitForCondition(() => {
+      snapshot = histogram.snapshot();
+      return "mochitest.follow-on:unknown:TEST" in snapshot;
+    });
+    Assert.ok("mochitest.follow-on:unknown:TEST" in snapshot,
+      "Histogram should have an entry for the follow-on search.");
+    Assert.deepEqual(snapshot["mochitest.follow-on:unknown:TEST"], {
+      counts: [ 1, 0, 0 ],
+      histogram_type: 4,
+      max: 2,
+      min: 1,
+      ranges: [ 0, 1, 2 ],
+      sum: 1,
+    }, "Histogram should have the correct snapshot data");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/followonsearch/test/browser/test.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8" />
+  <title>Follow-on Search Test</title>
+</head>
+<body>
+  <form method="get" action="test2.html">
+    <input type="text" name="mtfo" value="followonsearchtest"/>
+    <input type="text" name="mt" value="TEST"/>
+    <input type="text" name="m" value="test"/>
+    <input type="submit" id="submit" value="submit"/>
+  </form>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/followonsearch/test/browser/test2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8" />
+  <title>Follow-on Search Test Final Page</title>
+</head>
+<body></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/followonsearch/test/browser/testEngine.xml
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+                       xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+  <ShortName>Mochitest</ShortName>
+  <Description>Mochitest Engine</Description>
+  <InputEncoding>UTF-8</InputEncoding>
+  <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+  <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/extensions/followonsearch/test/browser/test.html?search" rel="searchform">
+    <Param name="m" value="{searchTerms}"/>
+    <Param name="mt" value="TEST"/>
+  </Url>
+</OpenSearchDescription>
--- a/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
+++ b/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
@@ -24,16 +24,21 @@ const TEST_ADDRESSES = [{
   organization: "Mozilla",
   "street-address": "123 Sesame Street",
   "address-level1": "AL",
   country: "DE",
   timesUsed: 1,
 }];
 
 add_task(async function test_DE_is_valid_testcase() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["extensions.formautofill.supportedCountries", "US,CA,DE"],
+    ],
+  });
   let chromeScript = SpecialPowers.loadChromeScript(function test_country_data() {
     ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
     let data = AddressDataLoader.getData("DE");
     /* global addMessageListener */
     addMessageListener("CheckSubKeys", () => {
       return !data.defaultLocale.sub_keys;
     });
   });
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -954,16 +954,36 @@ netmonitor.context.copyAllAsHar.accesske
 # LOCALIZATION NOTE (netmonitor.context.saveAllAsHar): This is the label displayed
 # on the context menu that saves all as HAR format
 netmonitor.context.saveAllAsHar=Save All As HAR
 
 # LOCALIZATION NOTE (netmonitor.context.saveAllAsHar.accesskey): This is the access key
 # for the Save All As HAR menu item displayed in the context menu for a network panel
 netmonitor.context.saveAllAsHar.accesskey=H
 
+# LOCALIZATION NOTE (netmonitor.context.importHar): This is the label displayed
+# on the context menu that imports HAR files
+netmonitor.context.importHar=Import…
+
+# LOCALIZATION NOTE (netmonitor.context.importHar.accesskey): This is the access key
+# for the Import HAR menu item displayed in the context menu for a network panel
+netmonitor.context.importHar.accesskey=I
+
+# LOCALIZATION NOTE (netmonitor.har.importHarDialogTitle): This is a label
+# used for import file open dialog
+netmonitor.har.importHarDialogTitle=Import HAR File
+
+# LOCALIZATION NOTE (netmonitor.har.importDialogHARFilter):
+# This string is displayed as a filter for importing HAR file
+netmonitor.har.importDialogHARFilter=HAR Files
+
+# LOCALIZATION NOTE (netmonitor.har.importDialogAllFilter):
+# This string is displayed as a filter for importing HAR file
+netmonitor.har.importDialogAllFilter=All Files
+
 # LOCALIZATION NOTE (netmonitor.context.editAndResend): This is the label displayed
 # on the context menu that opens a form to edit and resend the currently
 # displayed request
 netmonitor.context.editAndResend=Edit and Resend
 
 # LOCALIZATION NOTE (netmonitor.context.editAndResend.accesskey): This is the access key
 # for the "Edit and Resend" menu item displayed in the context menu for a request
 netmonitor.context.editAndResend.accesskey=E
--- a/devtools/client/netmonitor/src/components/App.js
+++ b/devtools/client/netmonitor/src/components/App.js
@@ -59,17 +59,19 @@ class App extends Component {
     return (
       div({className: "network-monitor"},
         !statisticsOpen ?
           DropHarHandler({
             actions,
             openSplitConsole,
           },
             MonitorPanel({
+              actions,
               connector,
+              openSplitConsole,
               sourceMapService,
               openLink,
             })
           ) : StatisticsPanel({
             connector
           }),
       )
     );
--- a/devtools/client/netmonitor/src/components/DropHarHandler.js
+++ b/devtools/client/netmonitor/src/components/DropHarHandler.js
@@ -5,19 +5,18 @@
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { L10N } = require("../utils/l10n");
 
-loader.lazyGetter(this, "HarImporter", function() {
-  return require("../har/har-importer").HarImporter;
-});
+loader.lazyRequireGetter(this, "HarMenuUtils",
+  "devtools/client/netmonitor/src/har/har-menu-utils", true);
 
 const { div } = dom;
 
 const DROP_HAR_FILES = L10N.getStr("netmonitor.label.dropHarFiles");
 
 /**
  * Helper component responsible for handling and  importing
  * dropped *.har files.
@@ -65,44 +64,34 @@ class DropHarHandler extends Component {
     event.preventDefault();
     stopDragging(findDOMNode(this));
 
     let files = event.dataTransfer.files;
     if (!files) {
       return;
     }
 
+    let {
+      actions,
+      openSplitConsole,
+    } = this.props;
+
     // Import only the first dragged file for now
     // See also:
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1438792
     if (files.length) {
       let file = files[0];
       readFile(file).then(har => {
         if (har) {
-          this.appendPreview(har);
+          HarMenuUtils.appendPreview(har, actions, openSplitConsole);
         }
       });
     }
   }
 
-  appendPreview(har) {
-    let {
-      openSplitConsole
-    } = this.props;
-
-    try {
-      let importer = new HarImporter(this.props.actions);
-      importer.import(har);
-    } catch (err) {
-      if (openSplitConsole) {
-        openSplitConsole("Error while processing HAR file: " + err.message);
-      }
-    }
-  }
-
   // Rendering
 
   render() {
     return (
       div({
         onDragEnter: this.onDragEnter,
         onDragOver: this.onDragOver,
         onDragExit: this.onDragExit,
--- a/devtools/client/netmonitor/src/components/MonitorPanel.js
+++ b/devtools/client/netmonitor/src/components/MonitorPanel.js
@@ -37,20 +37,23 @@ const MediaQuerySingleRow = window.match
 
 /**
  * Monitor panel component
  * The main panel for displaying various network request information
  */
 class MonitorPanel extends Component {
   static get propTypes() {
     return {
+      actions: PropTypes.object.isRequired,
       connector: PropTypes.object.isRequired,
       isEmpty: PropTypes.bool.isRequired,
       networkDetailsOpen: PropTypes.bool.isRequired,
       openNetworkDetails: PropTypes.func.isRequired,
+      // Callback for opening split console.
+      openSplitConsole: PropTypes.func,
       onNetworkDetailsResized: PropTypes.func.isRequired,
       request: PropTypes.object,
       selectedRequestVisible: PropTypes.bool.isRequired,
       sourceMapService: PropTypes.object,
       openLink: PropTypes.func,
       updateRequest: PropTypes.func.isRequired,
     };
   }
@@ -113,32 +116,36 @@ class MonitorPanel extends Component {
     return this.props.onNetworkDetailsResized(
       isVerticalSpliter ? width : null,
       isVerticalSpliter ? null : height
     );
   }
 
   render() {
     let {
+      actions,
       connector,
       isEmpty,
       networkDetailsOpen,
       openLink,
+      openSplitConsole,
       sourceMapService,
     } = this.props;
 
     let initialWidth = Services.prefs.getIntPref(
       "devtools.netmonitor.panes-network-details-width");
     let initialHeight = Services.prefs.getIntPref(
       "devtools.netmonitor.panes-network-details-height");
 
     return (
       div({ className: "monitor-panel" },
         Toolbar({
+          actions,
           connector,
+          openSplitConsole,
           singleRow: this.state.isSingleRow,
         }),
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: initialWidth,
           initialHeight: initialHeight,
           minSize: "50px",
           maxSize: "80%",
--- a/devtools/client/netmonitor/src/components/Toolbar.js
+++ b/devtools/client/netmonitor/src/components/Toolbar.js
@@ -53,16 +53,17 @@ loader.lazyRequireGetter(this, "HarMenuU
  * Network monitor toolbar component.
  *
  * Toolbar contains a set of useful tools to control network requests
  * as well as set of filters for filtering the content.
  */
 class Toolbar extends Component {
   static get propTypes() {
     return {
+      actions: PropTypes.object.isRequired,
       connector: PropTypes.object.isRequired,
       toggleRecording: PropTypes.func.isRequired,
       recording: PropTypes.bool.isRequired,
       clearRequests: PropTypes.func.isRequired,
       // List of currently displayed requests (i.e. filtered & sorted).
       displayedRequests: PropTypes.array.isRequired,
       requestFilterTypes: PropTypes.object.isRequired,
       setRequestFilterText: PropTypes.func.isRequired,
@@ -72,16 +73,18 @@ class Toolbar extends Component {
       disableBrowserCache: PropTypes.func.isRequired,
       toggleBrowserCache: PropTypes.func.isRequired,
       browserCacheDisabled: PropTypes.bool.isRequired,
       toggleRequestFilterType: PropTypes.func.isRequired,
       filteredRequests: PropTypes.array.isRequired,
       // Set to true if there is enough horizontal space
       // and the toolbar needs just one row.
       singleRow: PropTypes.bool.isRequired,
+      // Callback for opening split console.
+      openSplitConsole: PropTypes.func,
     };
   }
 
   constructor(props) {
     super(props);
     this.autocompleteProvider = this.autocompleteProvider.bind(this);
     this.onSearchBoxFocus = this.onSearchBoxFocus.bind(this);
     this.toggleRequestFilterType = this.toggleRequestFilterType.bind(this);
@@ -269,23 +272,34 @@ class Toolbar extends Component {
       onClick: evt => {
         this.showHarMenu(evt.target);
       },
     });
   }
 
   showHarMenu(menuButton) {
     const {
+      actions,
       connector,
-      displayedRequests
+      displayedRequests,
+      openSplitConsole,
     } = this.props;
 
     let menuItems = [];
 
     menuItems.push({
+      id: "request-list-context-import-har",
+      label: L10N.getStr("netmonitor.context.importHar"),
+      accesskey: L10N.getStr("netmonitor.context.importHar.accesskey"),
+      click: () => HarMenuUtils.openHarFile(actions, openSplitConsole),
+    });
+
+    menuItems.push("-");
+
+    menuItems.push({
       id: "request-list-context-save-all-as-har",
       label: L10N.getStr("netmonitor.context.saveAllAsHar"),
       accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
       disabled: !displayedRequests.length,
       click: () => HarMenuUtils.saveAllAsHar(displayedRequests, connector),
     });
 
     menuItems.push({
--- a/devtools/client/netmonitor/src/har/har-menu-utils.js
+++ b/devtools/client/netmonitor/src/har/har-menu-utils.js
@@ -1,17 +1,24 @@
 /* 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/. */
 
 /* eslint-disable mozilla/reject-some-requires */
 
 "use strict";
 
-loader.lazyRequireGetter(this, "HarExporter", "devtools/client/netmonitor/src/har/har-exporter", true);
+const { L10N } = require("../utils/l10n");
+
+loader.lazyRequireGetter(this, "HarExporter",
+  "devtools/client/netmonitor/src/har/har-exporter", true);
+
+loader.lazyGetter(this, "HarImporter", function() {
+  return require("../har/har-importer").HarImporter;
+});
 
 /**
  * Helper object with HAR related context menu actions.
  */
 var HarMenuUtils = {
   /**
    * Copy HAR from the network panel content to the clipboard.
    */
@@ -25,18 +32,66 @@ var HarMenuUtils = {
   saveAllAsHar(requests, connector) {
     // This will not work in launchpad
     // document.execCommand(‘cut’/‘copy’) was denied because it was not called from
     // inside a short running user-generated event handler.
     // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
     return HarExporter.save(this.getDefaultHarOptions(requests, connector));
   },
 
+  /**
+   * Import HAR file and preview its content in the Network panel.
+   */
+  openHarFile(actions, openSplitConsole) {
+    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+    fp.init(window, L10N.getStr("netmonitor.har.importHarDialogTitle"),
+      Ci.nsIFilePicker.modeOpen);
+
+    // Append file filters
+    fp.appendFilter(L10N.getStr("netmonitor.har.importDialogHARFilter"), "*.har");
+    fp.appendFilter(L10N.getStr("netmonitor.har.importDialogAllFilter"), "*.*");
+
+    fp.open(rv => {
+      if (rv == Ci.nsIFilePicker.returnOK) {
+        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+        file.initWithPath(fp.file.path);
+        readFile(file).then(har => {
+          if (har) {
+            this.appendPreview(har, actions, openSplitConsole);
+          }
+        });
+      }
+    });
+  },
+
+  appendPreview(har, actions, openSplitConsole) {
+    try {
+      let importer = new HarImporter(actions);
+      importer.import(har);
+    } catch (err) {
+      if (openSplitConsole) {
+        openSplitConsole("Error while processing HAR file: " + err.message);
+      }
+    }
+  },
+
   getDefaultHarOptions(requests, connector) {
     return {
       connector: connector,
       items: requests,
     };
   },
 };
 
+// Helpers
+
+function readFile(file) {
+  return new Promise(resolve => {
+    const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+    OS.File.read(file.path).then(data => {
+      let decoder = new TextDecoder();
+      resolve(decoder.decode(data));
+    });
+  });
+}
+
 // Exports from this module
 exports.HarMenuUtils = HarMenuUtils;
--- a/devtools/client/netmonitor/src/utils/menu.js
+++ b/devtools/client/netmonitor/src/utils/menu.js
@@ -19,16 +19,20 @@ const MenuItem = require("devtools/clien
 function showMenu(items, options) {
   if (items.length === 0) {
     return;
   }
 
   // Build the menu object from provided menu items.
   let menu = new Menu();
   items.forEach((item) => {
+    if (item == "-") {
+      item = { type: "separator" };
+    }
+
     let menuItem = new MenuItem(item);
     let subItems = item.submenu;
 
     if (subItems) {
       let subMenu = new Menu();
       subItems.forEach((subItem) => {
         subMenu.append(new MenuItem(subItem));
       });
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -487,17 +487,24 @@ TableWidget.prototype = {
    * rows.
    */
   onKeydown: function(event) {
     // If we are in edit mode bail out.
     if (this._editableFieldsEngine && this._editableFieldsEngine.isEditing) {
       return;
     }
 
-    let selectedCell = this.tbody.querySelector(".theme-selected");
+    // We need to get the first *visible* selected cell. Some columns are hidden
+    // e.g. because they contain a unique compound key for cookies that is never
+    // displayed in the UI. To do this we get all selected cells and filter out
+    // any that are hidden.
+    const selectedCells = [...this.tbody.querySelectorAll(".theme-selected")]
+                                        .filter(cell => cell.clientWidth > 0);
+    // Select the first visible selected cell.
+    const selectedCell = selectedCells[0];
     if (!selectedCell) {
       return;
     }
 
     let colName;
     let column;
     let visibleCells;
     let index;
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -534,17 +534,22 @@ class StorageUI {
   }
 
   /**
    * Handle changed items received by onEdit
    *
    * @param {object} See onEdit docs
    */
   async handleChangedItems(changed) {
-    let [type, host, db, objectStore] = this.tree.selectedItem;
+    const selectedItem = this.tree.selectedItem;
+    if (!selectedItem) {
+      return;
+    }
+
+    let [type, host, db, objectStore] = selectedItem;
     if (!changed[type] || !changed[type][host] ||
         changed[type][host].length == 0) {
       return;
     }
     try {
       let toUpdate = [];
       for (let name of changed[type][host]) {
         let names = JSON.parse(name);
@@ -911,16 +916,20 @@ class StorageUI {
    * Select handler for the storage tree. Fetches details of the selected item
    * from the storage details and populates the storage tree.
    *
    * @param {array} item
    *        An array of ids which represent the location of the selected item in
    *        the storage tree
    */
   async onHostSelect(item) {
+    if (!item) {
+      return;
+    }
+
     this.table.clear();
     this.hideSidebar();
     this.searchBox.value = "";
 
     let [type, host] = item;
     this.table.host = host;
     this.table.datatype = type;
 
@@ -1218,18 +1227,23 @@ class StorageUI {
   async onRefreshTable() {
     await this.onHostSelect(this.tree.selectedItem);
   }
 
   /**
    * Handles adding an item from the storage
    */
   onAddItem() {
-    let front = this.getCurrentFront();
-    let [, host] = this.tree.selectedItem;
+    const selectedItem = this.tree.selectedItem;
+    if (!selectedItem) {
+      return;
+    }
+
+    const front = this.getCurrentFront();
+    const [, host] = selectedItem;
 
     // Prepare to scroll into view.
     this.table.scrollIntoViewOnUpdate = true;
     this.table.editBookmark = createGUID();
     front.addItem(this.table.editBookmark, host);
   }
 
   /**
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -520,17 +520,17 @@ class ShapesHighlighter extends AutoRefr
 
         // Calculate constraints for a virtual viewport which ensures that a dragged
         // marker remains visible even at the edges of the actual viewport.
         this.setViewport(BASE_MARKER_SIZE);
         break;
       case "mouseup":
         if (this[_dragging]) {
           this[_dragging] = null;
-          this._handleMarkerHover(this.hoveredPoint);
+          this._handleMarkerHover(null);
         }
         break;
       case "mousemove":
         if (!this[_dragging]) {
           this._handleMouseMoveNotDragging(pageX, pageY);
           return;
         }
         event.stopPropagation();
@@ -610,16 +610,17 @@ class ShapesHighlighter extends AutoRefr
 
       let ratioX = this.getUnitToPixelRatio(unitX, width);
       let ratioY = this.getUnitToPixelRatio(unitY, height);
       return { unitX, unitY, valueX, valueY, ratioX, ratioY };
     });
     this[_dragging] = { type, pointsInfo, x: pageX, y: pageY, bb: this.boundingBox,
                         matrix: this.transformMatrix,
                         transformedBB: this.transformedBoundingBox };
+    this._handleMarkerHover(this.hoveredPoint);
   }
 
   /**
    * Handle a click in transform mode while highlighting a circle.
    * @param {Number} pageX the x coordinate of the mouse.
    * @param {Number} pageY the y coordinate of the mouse.
    * @param {String} type the type of transform handle that was clicked.
    */
@@ -874,20 +875,18 @@ class ShapesHighlighter extends AutoRefr
   _transformPolygon() {
     let { pointsInfo } = this[_dragging];
 
     let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
     polygonDef += pointsInfo.map(point => {
       let { unitX, unitY, valueX, valueY, ratioX, ratioY } = point;
       let vector = [valueX / ratioX, valueY / ratioY];
       let [newX, newY] = apply(this.transformMatrix, vector);
-      let precisionX = getDecimalPrecision(unitX);
-      let precisionY = getDecimalPrecision(unitY);
-      newX = (newX * ratioX).toFixed(precisionX);
-      newY = (newY * ratioY).toFixed(precisionY);
+      newX = round(newX * ratioX, unitX);
+      newY = round(newY * ratioY, unitY);
 
       return `${newX}${unitX} ${newY}${unitY}`;
     }).join(", ");
     polygonDef = `polygon(${polygonDef}) ${this.geometryBox}`.trim();
 
     this.emit("highlighter-event", { type: "shape-change", value: polygonDef });
   }
 
@@ -901,21 +900,24 @@ class ShapesHighlighter extends AutoRefr
           ratioX, ratioY, ratioRad } = this[_dragging];
     let { radius } = this.coordUnits;
 
     let [newCx, newCy] = apply(this.transformMatrix, [valueX / ratioX, valueY / ratioY]);
     if (transX !== null) {
       // As part of scaling, the shape is translated to be tangent to the line y=0.
       // To get the new radius, we translate the new cx back to that point and get
       // the distance to the line y=0.
-      radius = `${Math.abs((newCx - transX) * ratioRad)}${unitRad}`;
+      radius = round(Math.abs((newCx - transX) * ratioRad), unitRad);
+      radius = `${radius}${unitRad}`;
     }
 
-    let circleDef = `circle(${radius} at ${newCx * ratioX}${unitX} ` +
-        `${newCy * ratioY}${unitY}) ${this.geometryBox}`.trim();
+    newCx = round(newCx * ratioX, unitX);
+    newCy = round(newCy * ratioY, unitY);
+    let circleDef = `circle(${radius} at ${newCx}${unitX} ${newCy}${unitY})` +
+        ` ${this.geometryBox}`.trim();
     this.emit("highlighter-event", { type: "shape-change", value: circleDef });
   }
 
   /**
    * Transform an ellipse depending on the current transformation matrix.
    * @param {Number} transX the number of pixels the shape is translated on the x axis
    *                 before scaling
    * @param {Number} transY the number of pixels the shape is translated on the y axis
@@ -926,49 +928,58 @@ class ShapesHighlighter extends AutoRefr
           ratioX, ratioY, ratioRX, ratioRY } = this[_dragging];
     let { rx, ry } = this.coordUnits;
 
     let [newCx, newCy] = apply(this.transformMatrix, [valueX / ratioX, valueY / ratioY]);
     if (transX !== null && transY !== null) {
       // As part of scaling, the shape is translated to be tangent to the lines y=0 & x=0.
       // To get the new radii, we translate the new center back to that point and get the
       // distances to the line x=0 and y=0.
-      rx = `${Math.abs((newCx - transX) * ratioRX)}${unitRX}`;
-      ry = `${Math.abs((newCy - transY) * ratioRY)}${unitRY}`;
+      rx = round(Math.abs((newCx - transX) * ratioRX), unitRX);
+      rx = `${rx}${unitRX}`;
+      ry = round(Math.abs((newCy - transY) * ratioRY), unitRY);
+      ry = `${ry}${unitRY}`;
     }
 
-    let ellipseDef = `ellipse(${rx} ${ry} at ${newCx * ratioX}${unitX} ` +
-          `${newCy * ratioY}${unitY}) ${this.geometryBox}`.trim();
+    newCx = round(newCx * ratioX, unitX);
+    newCy = round(newCy * ratioY, unitY);
+
+    let centerStr = `${newCx}${unitX} ${newCy}${unitY}`;
+    let ellipseDef = `ellipse(${rx} ${ry} at ${centerStr}) ${this.geometryBox}`.trim();
     this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
   }
 
   /**
    * Transform an inset depending on the current transformation matrix.
    */
   _transformInset() {
     let { top, left, right, bottom } = this[_dragging].pointsInfo;
     let { width, height } = this.currentDimensions;
 
     let topLeft = [ left.value / left.ratio, top.value / top.ratio ];
     let [newLeft, newTop] = apply(this.transformMatrix, topLeft);
-    newLeft = `${newLeft * left.ratio}${left.unit}`;
-    newTop = `${newTop * top.ratio}${top.unit}`;
+    newLeft = round(newLeft * left.ratio, left.unit);
+    newLeft = `${newLeft}${left.unit}`;
+    newTop = round(newTop * top.ratio, top.unit);
+    newTop = `${newTop}${top.unit}`;
 
     // Right and bottom values are relative to the right and bottom edges of the
     // element, so convert to the value relative to the left/top edges before scaling
     // and convert back.
     let bottomRight = [ width - right.value / right.ratio,
                         height - bottom.value / bottom.ratio ];
     let [newRight, newBottom] = apply(this.transformMatrix, bottomRight);
-    newRight = `${(width - newRight) * right.ratio}${right.unit}`;
-    newBottom = `${(height - newBottom) * bottom.ratio}${bottom.unit}`;
+    newRight = round((width - newRight) * right.ratio, right.unit);
+    newRight = `${newRight}${right.unit}`;
+    newBottom = round((height - newBottom) * bottom.ratio, bottom.unit);
+    newBottom = `${newBottom}${bottom.unit}`;
 
-    let round = this.insetRound;
-    let insetDef = (round) ?
-          `inset(${newTop} ${newRight} ${newBottom} ${newLeft} round ${round})` :
+    let insetDef = (this.insetRound) ?
+          `inset(${newTop} ${newRight} ${newBottom} ${newLeft} round ${this.insetRound})`
+          :
           `inset(${newTop} ${newRight} ${newBottom} ${newLeft})`;
     insetDef += (this.geometryBox) ? this.geometryBox : "";
 
     this.emit("highlighter-event", { type: "shape-change", value: insetDef });
   }
 
   /**
    * Handle a click when highlighting a polygon.
@@ -1004,20 +1015,18 @@ class ShapesHighlighter extends AutoRefr
    * the element style.
    * @param {Number} pageX the new x coordinate of the point
    * @param {Number} pageY the new y coordinate of the point
    */
   _handlePolygonMove(pageX, pageY) {
     let { point, unitX, unitY, valueX, valueY, ratioX, ratioY, x, y } = this[_dragging];
     let deltaX = (pageX - x) * ratioX;
     let deltaY = (pageY - y) * ratioY;
-    let precisionX = getDecimalPrecision(unitX);
-    let precisionY = getDecimalPrecision(unitY);
-    let newX = (valueX + deltaX).toFixed(precisionX);
-    let newY = (valueY + deltaY).toFixed(precisionY);
+    let newX = round(valueX + deltaX, unitX);
+    let newY = round(valueY + deltaY, unitY);
 
     let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
     polygonDef += this.coordUnits.map((coords, i) => {
       return (i === point) ?
         `${newX}${unitX} ${newY}${unitY}` : `${coords[0]} ${coords[1]}`;
     }).join(", ");
     polygonDef = `polygon(${polygonDef}) ${this.geometryBox}`.trim();
 
@@ -1115,31 +1124,31 @@ class ShapesHighlighter extends AutoRefr
    */
   _handleCircleMove(point, pageX, pageY) {
     let { radius, cx, cy } = this.coordUnits;
 
     if (point === "center") {
       let { unitX, unitY, valueX, valueY, ratioX, ratioY, x, y} = this[_dragging];
       let deltaX = (pageX - x) * ratioX;
       let deltaY = (pageY - y) * ratioY;
-      let newCx = `${valueX + deltaX}${unitX}`;
-      let newCy = `${valueY + deltaY}${unitY}`;
+      let newCx = `${round(valueX + deltaX, unitX)}${unitX}`;
+      let newCy = `${round(valueY + deltaY, unitY)}${unitY}`;
       // if not defined by the user, geometryBox will be an empty string; trim() cleans up
       let circleDef = `circle(${radius} at ${newCx} ${newCy}) ${this.geometryBox}`.trim();
 
       this.emit("highlighter-event", { type: "shape-change", value: circleDef });
     } else if (point === "radius") {
       let { value, unit, origRadius, ratio } = this[_dragging];
       // convert center point to px, then get distance between center and mouse.
       let { x: pageCx, y: pageCy } = this.convertPercentToPageCoords(this.coordinates.cx,
                                                                      this.coordinates.cy);
       let newRadiusPx = getDistance(pageCx, pageCy, pageX, pageY);
 
       let delta = (newRadiusPx - origRadius) * ratio;
-      let newRadius = `${value + delta}${unit}`;
+      let newRadius = `${round(value + delta, unit)}${unit}`;
 
       let circleDef = `circle(${newRadius} at ${cx} ${cy}) ${this.geometryBox}`.trim();
 
       this.emit("highlighter-event", { type: "shape-change", value: circleDef });
     }
   }
 
   /**
@@ -1203,39 +1212,39 @@ class ShapesHighlighter extends AutoRefr
   _handleEllipseMove(point, pageX, pageY) {
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
     let { rx, ry, cx, cy } = this.coordUnits;
 
     if (point === "center") {
       let { unitX, unitY, valueX, valueY, ratioX, ratioY, x, y} = this[_dragging];
       let deltaX = (pageX - x) * ratioX;
       let deltaY = (pageY - y) * ratioY;
-      let newCx = `${valueX + deltaX}${unitX}`;
-      let newCy = `${valueY + deltaY}${unitY}`;
+      let newCx = `${round(valueX + deltaX, unitX)}${unitX}`;
+      let newCy = `${round(valueY + deltaY, unitY)}${unitY}`;
       let ellipseDef =
         `ellipse(${rx} ${ry} at ${newCx} ${newCy}) ${this.geometryBox}`.trim();
 
       this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
     } else if (point === "rx") {
       let { value, unit, origRadius, ratio } = this[_dragging];
       let newRadiusPercent = Math.abs(percentX - this.coordinates.cx);
       let { width } = this.currentDimensions;
       let delta = ((newRadiusPercent / 100 * width) - origRadius) * ratio;
-      let newRadius = `${value + delta}${unit}`;
+      let newRadius = `${round(value + delta, unit)}${unit}`;
 
       let ellipseDef =
         `ellipse(${newRadius} ${ry} at ${cx} ${cy}) ${this.geometryBox}`.trim();
 
       this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
     } else if (point === "ry") {
       let { value, unit, origRadius, ratio } = this[_dragging];
       let newRadiusPercent = Math.abs(percentY - this.coordinates.cy);
       let { height } = this.currentDimensions;
       let delta = ((newRadiusPercent / 100 * height) - origRadius) * ratio;
-      let newRadius = `${value + delta}${unit}`;
+      let newRadius = `${round(value + delta, unit)}${unit}`;
 
       let ellipseDef =
         `ellipse(${rx} ${newRadius} at ${cx} ${cy}) ${this.geometryBox}`.trim();
 
       this.emit("highlighter-event", { type: "shape-change", value: ellipseDef });
     }
   }
 
@@ -1271,34 +1280,34 @@ class ShapesHighlighter extends AutoRefr
    * @param {Number} pageX the x coordinate of the mouse position, in terms of %
    *        relative to the element
    * @param {Number} pageY the y coordinate of the mouse position, in terms of %
    *        relative to the element
    * @memberof ShapesHighlighter
    */
   _handleInsetMove(point, pageX, pageY) {
     let { top, left, right, bottom } = this.coordUnits;
-    let round = this.insetRound;
     let { value, origValue, unit, ratio } = this[_dragging];
 
     if (point === "left") {
       let delta = (pageX - origValue) * ratio;
-      left = `${value + delta}${unit}`;
+      left = `${round(value + delta, unit)}${unit}`;
     } else if (point === "right") {
       let delta = (pageX - origValue) * ratio;
-      right = `${value - delta}${unit}`;
+      right = `${round(value - delta, unit)}${unit}`;
     } else if (point === "top") {
       let delta = (pageY - origValue) * ratio;
-      top = `${value + delta}${unit}`;
+      top = `${round(value + delta, unit)}${unit}`;
     } else if (point === "bottom") {
       let delta = (pageY - origValue) * ratio;
-      bottom = `${value - delta}${unit}`;
+      bottom = `${round(value - delta, unit)}${unit}`;
     }
-    let insetDef = (round) ?
-      `inset(${top} ${right} ${bottom} ${left} round ${round})` :
+
+    let insetDef = (this.insetRound) ?
+      `inset(${top} ${right} ${bottom} ${left} round ${this.insetRound})` :
       `inset(${top} ${right} ${bottom} ${left})`;
 
     insetDef += (this.geometryBox) ? this.geometryBox : "";
 
     this.emit("highlighter-event", { type: "shape-change", value: insetDef });
   }
 
   _handleMouseMoveNotDragging(pageX, pageY) {
@@ -1371,17 +1380,19 @@ class ShapesHighlighter extends AutoRefr
         { pointName: "scale-se", x: se[0], y: se[1], anchor: "nw" },
         { pointName: "scale-ne", x: ne[0], y: ne[1], anchor: "sw" },
         { pointName: "scale-sw", x: sw[0], y: sw[1], anchor: "ne" },
         { pointName: "scale-nw", x: nw[0], y: nw[1], anchor: "se" },
         { pointName: "scale-n", x: n[0], y: n[1], anchor: "s" },
         { pointName: "scale-s", x: s[0], y: s[1], anchor: "n" },
         { pointName: "scale-e", x: e[0], y: e[1], anchor: "w" },
         { pointName: "scale-w", x: w[0], y: w[1], anchor: "e" },
-        { pointName: "rotate", x: rotatePoint[0], y: rotatePoint[1], cursor: "grab" },
+        { pointName: "rotate", x: rotatePoint[0], y: rotatePoint[1],
+          cursor: hoverCursor
+        },
       ];
 
       for (let { pointName, x, y, cursor, anchor } of points) {
         if (point === pointName) {
           this._drawHoverMarker([[x, y]]);
 
           // If the point is a scale handle, we will need to determine the direction
           // of the resize cursor based on the position of the handle relative to its
@@ -1604,18 +1615,17 @@ class ShapesHighlighter extends AutoRefr
       if (distance <= clickWidth &&
           Math.min(x1, x2) - clickWidth <= pageX &&
           pageX <= Math.max(x1, x2) + clickWidth &&
           Math.min(y1, y2) - clickWidth <= pageY &&
           pageY <= Math.max(y1, y2) + clickWidth) {
         // Get the point on the line closest to the clicked point.
         let [newX, newY] = projection(x1, y1, x2, y2, pageX, pageY);
         // Default unit for new points is percentages
-        let precision = getDecimalPrecision("%");
-        this._addPolygonPoint(i, newX.toFixed(precision), newY.toFixed(precision));
+        this._addPolygonPoint(i, round(newX, "%"), round(newY, "%"));
         return;
       }
     }
   }
 
   /**
    * Check if the center point or radius of the circle highlighter is at given coords
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
@@ -2815,19 +2825,16 @@ const getAnchorPoint = (type) => {
     anchor = anchor + "w";
   }
 
   return anchor;
 };
 
 /**
 * Get the decimal point precision for values depending on unit type.
-* Used as argument for `toFixed()` on coordinate values when:
-* - transforming shapes
-* - inserting new points on a polygon.
 * Only handle pixels and falsy values for now. Round them to the nearest integer value.
 * All other unit types round to two decimal points.
 *
 * @param {String|undefined} unitType any one of the accepted CSS unit types for position.
 * @return {Number} decimal precision when rounding a value
 */
 function getDecimalPrecision(unitType) {
   switch (unitType) {
@@ -2836,9 +2843,26 @@ function getDecimalPrecision(unitType) {
     case undefined:
       return 0;
     default:
       return 2;
   }
 }
 exports.getDecimalPrecision = getDecimalPrecision;
 
+/**
+ * Round up a numeric value to a fixed number of decimals depending on CSS unit type.
+ * Used when generating output shape values when:
+ * - transforming shapes
+ * - inserting new points on a polygon.
+ *
+ * @param {Number} number
+ *        Value to round up.
+ * @param {String} unitType
+ *        CSS unit type, like "px", "%", "em", "vh", etc.
+ * @return {Number}
+ *         Rounded value
+ */
+function round(number, unitType) {
+  return number.toFixed(getDecimalPrecision(unitType));
+}
+
 exports.ShapesHighlighter = ShapesHighlighter;
--- a/devtools/server/actors/object/symbol.js
+++ b/devtools/server/actors/object/symbol.js
@@ -1,80 +1,77 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; 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/. */
 
 "use strict";
 
+const protocol = require("devtools/shared/protocol");
+const { symbolSpec } = require("devtools/shared/specs/symbol");
 loader.lazyRequireGetter(this, "createValueGrip", "devtools/server/actors/object/utils", true);
 
 /**
  * Creates an actor for the specified symbol.
  *
  * @param symbol Symbol
  *        The symbol.
  */
-function SymbolActor(symbol) {
-  this.symbol = symbol;
-}
-
-SymbolActor.prototype = {
-  actorPrefix: "symbol",
+const SymbolActor = protocol.ActorClassWithSpec(symbolSpec, {
+  initialize(symbol) {
+    protocol.Actor.prototype.initialize.call(this);
+    this.symbol = symbol;
+  },
 
   rawValue: function() {
     return this.symbol;
   },
 
   destroy: function() {
     // Because symbolActors is not a weak map, we won't automatically leave
     // it so we need to manually leave on destroy so that we don't leak
     // memory.
     this._releaseActor();
   },
 
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
-  grip: function() {
+  form: function() {
     let form = {
-      type: "symbol",
+      type: this.typeName,
       actor: this.actorID,
     };
     let name = getSymbolName(this.symbol);
     if (name !== undefined) {
       // Create a grip for the name because it might be a longString.
       form.name = createValueGrip(name, this.registeredPool);
     }
     return form;
   },
 
   /**
    * Handle a request to release this SymbolActor instance.
    */
-  onRelease: function() {
+  release: function() {
     // TODO: also check if registeredPool === threadActor.threadLifetimePool
     // when the web console moves away from manually releasing pause-scoped
     // actors.
     this._releaseActor();
     this.registeredPool.removeActor(this);
     return {};
   },
 
   _releaseActor: function() {
     if (this.registeredPool && this.registeredPool.symbolActors) {
       delete this.registeredPool.symbolActors[this.symbol];
     }
   }
-};
-
-SymbolActor.prototype.requestTypes = {
-  "release": SymbolActor.prototype.onRelease
-};
+});
 
 const symbolProtoToString = Symbol.prototype.toString;
 
 function getSymbolName(symbol) {
   const name = symbolProtoToString.call(symbol).slice("Symbol(".length, -1);
   return name || undefined;
 }
 
@@ -87,21 +84,21 @@ function getSymbolName(symbol) {
  *        The actor pool where the new actor will be added.
  */
 function symbolGrip(sym, pool) {
   if (!pool.symbolActors) {
     pool.symbolActors = Object.create(null);
   }
 
   if (sym in pool.symbolActors) {
-    return pool.symbolActors[sym].grip();
+    return pool.symbolActors[sym].form();
   }
 
   let actor = new SymbolActor(sym);
   pool.addActor(actor);
   pool.symbolActors[sym] = actor;
-  return actor.grip();
+  return actor.form();
 }
 
 module.exports = {
   SymbolActor,
   symbolGrip,
 };
--- a/devtools/server/tests/unit/test_symbolactor.js
+++ b/devtools/server/tests/unit/test_symbolactor.js
@@ -3,17 +3,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const { SymbolActor } = require("devtools/server/actors/object/symbol");
 
 function run_test() {
   test_SA_destroy();
-  test_SA_grip();
+  test_SA_form();
   test_SA_raw();
 }
 
 const SYMBOL_NAME = "abc";
 const TEST_SYMBOL = Symbol(SYMBOL_NAME);
 
 function makeMockSymbolActor() {
   let symbol = TEST_SYMBOL;
@@ -30,20 +30,20 @@ function makeMockSymbolActor() {
 function test_SA_destroy() {
   let actor = makeMockSymbolActor();
   strictEqual(actor.registeredPool.symbolActors[TEST_SYMBOL], actor);
 
   actor.destroy();
   strictEqual(TEST_SYMBOL in actor.registeredPool.symbolActors, false);
 }
 
-function test_SA_grip() {
+function test_SA_form() {
   let actor = makeMockSymbolActor();
-  let grip = actor.grip();
-  strictEqual(grip.type, "symbol");
-  strictEqual(grip.actor, actor.actorID);
-  strictEqual(grip.name, SYMBOL_NAME);
+  let form = actor.form();
+  strictEqual(form.type, "symbol");
+  strictEqual(form.actor, actor.actorID);
+  strictEqual(form.name, SYMBOL_NAME);
 }
 
 function test_SA_raw() {
   let actor = makeMockSymbolActor();
   strictEqual(actor.rawValue(), TEST_SYMBOL);
 }
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -195,16 +195,21 @@ const Types = exports.__TypesForTests = 
     front: "devtools/shared/fronts/styles",
   },
   {
     types: ["mediarule", "stylesheet", "stylesheets"],
     spec: "devtools/shared/specs/stylesheets",
     front: "devtools/shared/fronts/stylesheets",
   },
   {
+    types: ["symbol"],
+    spec: "devtools/shared/specs/symbol",
+    front: null,
+  },
+  {
     types: ["symbolIterator"],
     spec: "devtools/shared/specs/symbol-iterator",
     front: null,
   },
   {
     types: ["tab"],
     spec: "devtools/shared/specs/tab",
     front: null,
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -36,16 +36,17 @@ DevToolsModules(
     'reflow.js',
     'script.js',
     'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'symbol-iterator.js',
+    'symbol.js',
     'tab.js',
     'timeline.js',
     'webaudio.js',
     'webextension-inspected-window.js',
     'webextension-parent.js',
     'webgl.js',
     'worker.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/symbol.js
@@ -0,0 +1,22 @@
+/* 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 {
+  generateActorSpec,
+} = require("devtools/shared/protocol");
+
+const symbolSpec = generateActorSpec({
+  typeName: "symbol",
+
+  methods: {
+    release: {
+      request: {},
+      response: {}
+    },
+  }
+});
+
+exports.symbolSpec = symbolSpec;
--- a/dom/media/ChannelMediaDecoder.cpp
+++ b/dom/media/ChannelMediaDecoder.cpp
@@ -613,17 +613,17 @@ ChannelMediaDecoder::MetadataLoaded(
   MediaDecoder::MetadataLoaded(Move(aInfo), Move(aTags), aEventVisibility);
   // Set mode to PLAYBACK after reading metadata.
   mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
 }
 
 nsCString
 ChannelMediaDecoder::GetDebugInfo()
 {
-  auto&& str = MediaDecoder::GetDebugInfo();
+  nsCString str = MediaDecoder::GetDebugInfo();
   if (mResource) {
     AppendStringIfNotEmpty(str, mResource->GetDebugInfo());
   }
   return str;
 }
 
 } // namespace mozilla
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -3801,17 +3801,17 @@ MediaDecoderStateMachine::GetDebugInfo()
     mDecodedAudioEndTime.ToMicroseconds(),
     mDecodedVideoEndTime.ToMicroseconds(),
     mAudioCompleted,
     mVideoCompleted,
     mStateObj->GetDebugInfo().get());
 
   AppendStringIfNotEmpty(str, mMediaSink->GetDebugInfo());
 
-  return str;
+  return Move(str);
 }
 
 RefPtr<MediaDecoder::DebugInfoPromise>
 MediaDecoderStateMachine::RequestDebugInfo()
 {
   using PromiseType = MediaDecoder::DebugInfoPromise;
   RefPtr<PromiseType::Private> p = new PromiseType::Private(__func__);
   RefPtr<MediaDecoderStateMachine> self = this;
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -943,16 +943,17 @@ class RTCPeerConnection {
     if (!sdp && action != Ci.IPeerConnection.kActionRollback) {
       throw new this._win.DOMException(
           "Empty or null SDP provided to setLocalDescription",
           "InvalidParameterError");
     }
 
     // The fippo butter finger filter AKA non-ASCII chars
     // Note: SDP allows non-ASCII character in the subject (who cares?)
+    // eslint-disable-next-line no-control-regex
     let pos = sdp.search(/[^\u0000-\u007f]/);
     if (pos != -1) {
       throw new this._win.DOMException(
           "SDP contains non ASCII characters at position " + pos,
           "InvalidParameterError");
     }
   }
 
--- a/dom/media/mediasink/AudioSinkWrapper.cpp
+++ b/dom/media/mediasink/AudioSinkWrapper.cpp
@@ -253,14 +253,14 @@ AudioSinkWrapper::GetDebugInfo()
   auto str =
     nsPrintfCString("AudioSinkWrapper: IsStarted=%d IsPlaying=%d AudioEnded=%d",
                     IsStarted(),
                     IsPlaying(),
                     mAudioEnded);
   if (mAudioSink) {
     AppendStringIfNotEmpty(str, mAudioSink->GetDebugInfo());
   }
-  return str;
+  return Move(str);
 }
 
 } // namespace media
 } // namespace mozilla
 
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -797,12 +797,12 @@ DecodedStream::GetDebugInfo()
                     this,
                     startTime,
                     mLastOutputTime.ToMicroseconds(),
                     mPlaying,
                     mData.get());
   if (mData) {
     AppendStringIfNotEmpty(str, mData->GetDebugInfo());
   }
-  return str;
+  return Move(str);
 }
 
 } // namespace mozilla
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -558,13 +558,13 @@ VideoSink::GetDebugInfo()
     IsPlaying(),
     VideoQueue().IsFinished(),
     VideoQueue().GetSize(),
     mVideoFrameEndTime.ToMicroseconds(),
     mHasVideo,
     mVideoSinkEndRequest.Exists(),
     mEndPromiseHolder.IsEmpty());
   AppendStringIfNotEmpty(str, mAudioSink->GetDebugInfo());
-  return str;
+  return Move(str);
 }
 
 } // namespace media
 } // namespace mozilla
--- a/mobile/locales/search/list.json
+++ b/mobile/locales/search/list.json
@@ -450,17 +450,17 @@
         "visibleDefaultEngines": [
           "google", "danawa-kr", "twitter", "daum-kr", "naver-kr"
         ]
       }
     },
     "lij": {
       "default": {
         "visibleDefaultEngines": [
-          "google", "amazondotcom", "twitter", "wikipedia"
+          "google", "bing", "amazon-it", "wikipedia-lij"
         ]
       }
     },
     "lo": {
       "default": {
         "visibleDefaultEngines": [
           "google", "bing", "twitter", "wikipedia-lo"
         ]
new file mode 100644
--- /dev/null
+++ b/mobile/locales/searchplugins/amazon-it.xml
@@ -0,0 +1,12 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.it</ShortName>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAABaFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////mQACAgH7+/uEhIT9mACFhYX9/f0JCQkFBQWwagBtQgAJBQANDQ36lgDr6+vj4+O7u7u3t7dDQ0M9PT0oKCgbGxvVgADGdwCqZgBYNQBCKAAkFgAcEQARCgDw8PDS0tKysrKtra2Xl5eSkpJtbW1dXV1aWlpLS0tHR0c5OTkzMzMuLi4qKirrjQDniwDehgC7cACZXACKUwBjOwBPLwA8JAAsGwAgEwD19fXn5+fZ2dnPz8/Ly8vHx8ekpKSdnZ2JiYl/f394eHggICD7lwD2lADRfgDPfACeXwCETwBdOAA4IgA0IAAWDgDd3d3W1ta/v7+oqKiPj497e3twcHBpaWljY2NWVlYUFBTsjgDiiADZgwDAcwC3bgCmYwCPVgB2RwBnPgBKLQAMCADt7e1fX19SUlIWFhYSEhLwkADujwCRVwB8SwB6SgBSMgAPCQBOQY7nAAAABnRSTlMA8si8ZBgxEvPEAAADLklEQVRo3u2aZ1MiMRiA0dO8WeWkd6miNEVF6c3ee++ent3r9e/fgm6iggJuMjfO7PMxs+wTkrcEJiqRluYmxIGm5hZVidZ3iBvvWkUBj/dTg7g+iCstqmbElWZVE+JKkwpxRhEoAkWgCBTBfxBoNVpEYCz41hexbbUBfBoPtQ8KzAUOm/huyui+kanAaYWn9HxmKBgwQxUizAT2NqjKHCNBZw9UZ2SejcAGzzHNROAAgqU92n8wDgSzwEIwBRLvNUhEmKGGGAOBQCJoRhqaJIIvDARDJPBJah0SQS8DgWbnPkjDZGiQCGaZRJEw7+ibnZ5yVn4paOdTrofptqO3JdAOH89O28ZHO7gIBtsnSEliL9AeBYDAQeAYA+ApmOsAroJ9oHQErFORMNs8sAPBfPC13CCYCjRbIGE1kjxgKOgDiQkNl1KxAxJ2aSjGUqAhEWShhwyWAlqag2Ssl4yF5QuOQSJU5RQQlC+ge2wlxxhakyzyBVF6VpTO1SGgDMgW2OFpA56DBwS0cgUfaB1qi5aOwXvwiD3ZeRAAyvautaInxOQKeuFl+uUKjKNQwcQuSMzIr6YDFd0gqDGO3W/LIYt+cPRk3SMCQvMWEBnpZ9PR7GNAmXTcZZsZwDJEzmaFnF6vv/HXFnQJVXt+NDhS7mfbYTLn2GTIKX0oHcd3eGoKXK7ccwnR6TSiqmQWsMRiTUFxGbt9qDGuvfmC/7Z7PY3x99p7YLjAeClrQK+gG+Ozejb5So3xj0wRNULxt6cbFTFerSuKfEmMsVr35xbVx6beLT6/gdYwvqkvTA2XaixiSum7aq9LNlV6OOlDaAUnhXrzYH0Z3+HK5PzoOfze9HJ5KgsZA0LCCdY3kGjej1gicb66lvcLj1ZlY23Vc4rv0RWQSA4nDI1kspAtKSjquMud0ul05+6lU9PD8YsCKqMzrTdaKnweE65B4meXtBmm7Ctq0aY+9YLjJO0TaGTkX1ns/npXlhZwBXHdVZ68XfYPEMPG9aXHnYwvYvViwnW28stL4peJgFJ9zm/qb01FgJAiUASKACFFUAH3SwPcrz1wv7jB++oJ/8sz3K///AMH5R5E/GGrogAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.it/gp/aw/s">
+  <Param name="k" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.amazon.com/</SearchForm>
+</SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/mobile/locales/searchplugins/wikipedia-lij.xml
@@ -0,0 +1,23 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (lij)</ShortName>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAA51BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD////7+/vl5eWxsbH9/f3Ly8tzc3P39/fe3t5cXFzz8/Pt7e3i4uI3NzcaGhoHBwcFBQX5+fnIyMhTU1MXFxcMDAzx8fHX19fFxcXCwsK7u7u1tbWenp6WlpZ8fHxXV1ctLS319fXn5+fV1dWsrKySkpJnZ2cgICDv7+/p6eng4ODQ0NC9vb24uLikpKSPj4+JiYmCgoJiYmJCQkI7OzsyMjIpKSkQEBCoqKigoKCdnZ14eHhubm5PT09LS0tERETk5OTb29unp6c0NDQkJCQOGD7BAAAAB3RSTlMA8si8ZBhlc+JuAAAAAw1JREFUaN7tmtluIjEQRYEhUxd6YYew7xAggRAgCQmB7Ov8//eM3Zg2nXkJvUjz4POAVEbyQXaVy0gOMaKRMAVAOBINcY5+UWD8OmKCIOaXhlDoNwVKNBShQImEwhQo4RAFjBIogRIogRIogRIoQWCCYXqaHJVaWrtl1IfpYp3GiczFqLHQ5kY9m0l0qVM8Txrs+8ZomkhSKpbOzlraolSP15o/EtxoU9gk2iZVujM7HnVe6eUhKaL4aYrWXWMXfd7+dIlScWxpbsjibQiLPm255sH5jYjKOoCEecge5LcTxqokuItZv5F2DID0iR21ALwctslfunNCurKEBRJkgBTZdIDOoVl0CU7FjgsZHpsi+gNoJGlisDlUkC+C8SQHejzOiqCLgXRTNYazw+vg8tsaHZ+DMd7ZlyR5Qix/uOAEgGOhlzy+EPLMMUmyOHVTyQ0wDBlXB2Dw1FzreCZJGfhyIyiDs/qWSEkiarNPxxaX3J1FWcCRLJXidtFW7MO5lmV3AhMMPS8HHsH4oBJmtEcHF+ROUEgD2E/AiVXO98AtSaoJmC4F1AUjXZAD97Bo0R491ApuBRNdVq/FGhz9hPaI48p9w1mI6rVpi1qQ9FmRuRe8iTPaJgfOi6NaTr20zDoYJUfOM2aOHM15EYwBOKbQwHm14wcY5FYgTzi5CHewMPZytO9aIHuj3MY53sF5s3M0S94EFV5bdiLeshQdgdGwc9T0IJC1VROHs4EHSgHY1XKflaFXQQ6y2MpIVIiSYDStgRLOvN/sDDCSImmv7cxaWVuu5z0LxJKU+Y1M9LE4GHOrsWnkXUDx3a5mRR8zRXFsisj5IeiBc0cmPkRxZMBY0BIGeRLIYmJ8smnLDmVuiL5ngWxksUd5JhVqYEzxTv4I1tiSc9Y38OSTgOZwnki0GYhe55Pg1VqjCUnORLf2SWAVb5fIeYPRJ/4JnoFa1THSATTyT3BcQ++f+8DKRwGN299Hltr/+zdWCZRACZRACZSAlEAJDifwRwPBP3uIUqBEg356EvzjmcCf//wFVbAkwfl+5CMAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://lij.wikipedia.org/w/api.php">
+  <Param name="action" value="opensearch"/>
+  <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://lij.wikipedia.org/wiki/Speçiale:Riçerca">
+  <Param name="search" value="{searchTerms}"/>
+  <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<!-- Search activity -->
+<Url type="text/html" method="GET" rel="mobile" template="https://lij.m.wikipedia.org/wiki/Speçiale:Riçerca">
+  <Param name="search" value="{searchTerms}"/>
+  <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>https://lij.wikipedia.org/wiki/Speçiale:Riçerca</SearchForm>
+</SearchPlugin>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -233,17 +233,17 @@ pref("dom.keyboardevent.dispatch_during_
 pref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", true);
 // Blacklist of domains of web apps which are not aware of strict keypress
 // dispatching behavior.  This is comma separated list.  If you need to match
 // all sub-domains, you can specify it as "*.example.com".  Additionally, you
 // can limit the path.  E.g., "example.com/foo" means "example.com/foo*".  So,
 // if you need to limit under a directory, the path should end with "/" like
 // "example.com/foo/".  Note that this cannot limit port number for now.
 pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys",
-     "docs.google.com,mail.google.com,hangouts.google.com,keep.google.com,inbox.google.com,*.etherpad.org/p/,etherpad.wikimedia.org/p/,board.net/p/,pad.riseup.net/p/,*.sandstorm.io,factor.cc/pad/,*.etherpad.fr/p/,piratenpad.de/p/,notes.typo3.org/p/,etherpad.net/p/,mensuel.framapad.org/p/,pad.ouvaton.coop/,pad.systemli.org/p/,pad.lqdn.fr/p/,public.etherpad-mozilla.org/p/,*.cloudron.me/p/,pad.aquilenet.fr/p/,free.primarypad.com/p/,pad.ondesk.work/p/,demo.maadix.org/etherpad/pads/");
+     "docs.google.com,mail.google.com,hangouts.google.com,keep.google.com,inbox.google.com,*.etherpad.org/p/,etherpad.wikimedia.org/p/,board.net/p/,pad.riseup.net/p/,*.sandstorm.io,factor.cc/pad/,*.etherpad.fr/p/,piratenpad.de/p/,notes.typo3.org/p/,etherpad.net/p/,*.framapad.org/p/,pad.ouvaton.coop/,pad.systemli.org/p/,pad.lqdn.fr/p/,public.etherpad-mozilla.org/p/,*.cloudron.me/p/,pad.aquilenet.fr/p/,free.primarypad.com/p/,pad.ondesk.work/p/,demo.maadix.org/etherpad/pads/");
 #else
 pref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", false);
 #endif
 
 // Whether the WebMIDI API is enabled
 pref("dom.webmidi.enabled", false);
 
 // Whether to enable the JavaScript start-up cache. This causes one of the first
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -35,19 +35,19 @@
       }
     },
     "ajv-keywords": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
       "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I="
     },
     "ansi-escapes": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
-      "integrity": "sha1-7D6LTp+AZPwCw6ybZfHCdb2o75I="
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
+      "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw=="
     },
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
     },
     "ansi-styles": {
       "version": "2.2.1",
@@ -121,66 +121,71 @@
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
       "requires": {
         "balanced-match": "1.0.0",
         "concat-map": "0.0.1"
       }
     },
+    "buffer-from": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
+      "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA=="
+    },
     "caller-path": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
       "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
       "requires": {
         "callsites": "0.2.0"
       }
     },
     "callsites": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
       "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo="
     },
     "chalk": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
-      "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+      "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
       "requires": {
         "ansi-styles": "3.2.1",
         "escape-string-regexp": "1.0.5",
-        "supports-color": "5.3.0"
+        "supports-color": "5.4.0"
       },
       "dependencies": {
         "ansi-styles": {
           "version": "3.2.1",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "requires": {
             "color-convert": "1.9.1"
           }
         },
         "supports-color": {
-          "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
-          "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
+          "version": "5.4.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+          "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
           "requires": {
             "has-flag": "3.0.0"
           }
         }
       }
     },
     "chardet": {
       "version": "0.4.2",
       "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
       "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
     },
     "circular-json": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
-      "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY="
+      "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A=="
     },
     "cli-cursor": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
       "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
       "requires": {
         "restore-cursor": "2.0.0"
       }
@@ -209,22 +214,23 @@
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
     },
     "concat-stream": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.1.tgz",
-      "integrity": "sha512-gslSSJx03QKa59cIKqeJO9HQ/WZMotvYJCuaUULrLpjj8oG40kV2Z+gz82pVxlTkOADi4PJxQPPfhl1ELYrrXw==",
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
       "requires": {
+        "buffer-from": "1.0.0",
         "inherits": "2.0.3",
-        "readable-stream": "2.3.5",
+        "readable-stream": "2.3.6",
         "typedarray": "0.0.6"
       }
     },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
@@ -253,27 +259,27 @@
     },
     "del": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
       "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
       "requires": {
         "globby": "5.0.0",
         "is-path-cwd": "1.0.0",
-        "is-path-in-cwd": "1.0.0",
+        "is-path-in-cwd": "1.0.1",
         "object-assign": "4.1.1",
         "pify": "2.3.0",
         "pinkie-promise": "2.0.1",
         "rimraf": "2.6.2"
       }
     },
     "doctrine": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
-      "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=",
+      "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
       "requires": {
         "esutils": "2.0.2"
       }
     },
     "dom-serializer": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
       "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
@@ -317,63 +323,64 @@
       "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
     },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
     },
     "eslint": {
-      "version": "4.18.2",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.2.tgz",
-      "integrity": "sha512-qy4i3wODqKMYfz9LUI8N2qYDkHkoieTbiHpMrYUI/WbjhXJQr7lI4VngixTgaG+yHX+NBCv7nW4hA0ShbvaNKw==",
+      "version": "4.19.1",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
+      "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
       "requires": {
         "ajv": "5.5.2",
         "babel-code-frame": "6.26.0",
-        "chalk": "2.3.2",
-        "concat-stream": "1.6.1",
+        "chalk": "2.4.1",
+        "concat-stream": "1.6.2",
         "cross-spawn": "5.1.0",
         "debug": "3.1.0",
         "doctrine": "2.1.0",
         "eslint-scope": "3.7.1",
         "eslint-visitor-keys": "1.0.0",
         "espree": "3.5.4",
-        "esquery": "1.0.0",
+        "esquery": "1.0.1",
         "esutils": "2.0.2",
         "file-entry-cache": "2.0.0",
         "functional-red-black-tree": "1.0.1",
         "glob": "7.1.2",
-        "globals": "11.3.0",
-        "ignore": "3.3.7",
+        "globals": "11.5.0",
+        "ignore": "3.3.8",
         "imurmurhash": "0.1.4",
         "inquirer": "3.3.0",
         "is-resolvable": "1.1.0",
         "js-yaml": "3.11.0",
         "json-stable-stringify-without-jsonify": "1.0.1",
         "levn": "0.3.0",
-        "lodash": "4.17.5",
+        "lodash": "4.17.10",
         "minimatch": "3.0.4",
         "mkdirp": "0.5.1",
         "natural-compare": "1.4.0",
         "optionator": "0.8.2",
         "path-is-inside": "1.0.2",
         "pluralize": "7.0.0",
         "progress": "2.0.0",
+        "regexpp": "1.1.0",
         "require-uncached": "1.0.3",
         "semver": "5.5.0",
         "strip-ansi": "4.0.0",
         "strip-json-comments": "2.0.1",
         "table": "4.0.2",
         "text-table": "0.2.0"
       }
     },
     "eslint-plugin-html": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-4.0.2.tgz",
-      "integrity": "sha1-DlYUnkLC/8Pw32JhqLuWsanyKA0=",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-4.0.3.tgz",
+      "integrity": "sha512-ArFnlfQxwYSz/CP0zvk8Cy3MUhcDpT3o6jgO8eKD/b8ezcLVBrgkYzmMv+7S/ya+Yl9pN+Cz2tsgYp/zElkQzA==",
       "requires": {
         "htmlparser2": "3.9.2"
       }
     },
     "eslint-plugin-mozilla": {
       "version": "file:tools/lint/eslint/eslint-plugin-mozilla",
       "requires": {
         "ini-parser": "0.0.2",
@@ -422,19 +429,19 @@
       }
     },
     "esprima": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
       "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
     },
     "esquery": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
-      "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+      "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
       "requires": {
         "estraverse": "4.2.0"
       }
     },
     "esrecurse": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
       "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
@@ -448,22 +455,22 @@
       "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
     },
     "esutils": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
       "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
     },
     "external-editor": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz",
-      "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+      "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
       "requires": {
         "chardet": "0.4.2",
-        "iconv-lite": "0.4.19",
+        "iconv-lite": "0.4.21",
         "tmp": "0.0.33"
       }
     },
     "fast-deep-equal": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
       "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
     },
@@ -529,19 +536,19 @@
         "inflight": "1.0.6",
         "inherits": "2.0.3",
         "minimatch": "3.0.4",
         "once": "1.4.0",
         "path-is-absolute": "1.0.1"
       }
     },
     "globals": {
-      "version": "11.3.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz",
-      "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw=="
+      "version": "11.5.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz",
+      "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ=="
     },
     "globby": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
       "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
       "requires": {
         "array-union": "1.0.2",
         "arrify": "1.0.1",
@@ -582,28 +589,31 @@
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
       "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
       "requires": {
         "domelementtype": "1.3.0",
         "domhandler": "2.4.1",
         "domutils": "1.7.0",
         "entities": "1.1.1",
         "inherits": "2.0.3",
-        "readable-stream": "2.3.5"
+        "readable-stream": "2.3.6"
       }
     },
     "iconv-lite": {
-      "version": "0.4.19",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-      "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
+      "version": "0.4.21",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz",
+      "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==",
+      "requires": {
+        "safer-buffer": "2.1.2"
+      }
     },
     "ignore": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
-      "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA=="
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz",
+      "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg=="
     },
     "imurmurhash": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
       "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
     },
     "inflight": {
       "version": "1.0.6",
@@ -624,23 +634,23 @@
       "resolved": "https://registry.npmjs.org/ini-parser/-/ini-parser-0.0.2.tgz",
       "integrity": "sha1-+kF4flZ3Y7P/Zdel2alO23QHh+8="
     },
     "inquirer": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
       "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
       "requires": {
-        "ansi-escapes": "3.0.0",
-        "chalk": "2.3.2",
+        "ansi-escapes": "3.1.0",
+        "chalk": "2.4.1",
         "cli-cursor": "2.1.0",
         "cli-width": "2.2.0",
-        "external-editor": "2.1.0",
+        "external-editor": "2.2.0",
         "figures": "2.0.0",
-        "lodash": "4.17.5",
+        "lodash": "4.17.10",
         "mute-stream": "0.0.7",
         "run-async": "2.3.0",
         "rx-lite": "4.0.8",
         "rx-lite-aggregates": "4.0.8",
         "string-width": "2.1.1",
         "strip-ansi": "4.0.0",
         "through": "2.3.8"
       }
@@ -651,19 +661,19 @@
       "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
     },
     "is-path-cwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
       "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0="
     },
     "is-path-in-cwd": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
-      "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
+      "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
       "requires": {
         "is-path-inside": "1.0.1"
       }
     },
     "is-path-inside": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
       "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
@@ -725,19 +735,19 @@
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
       "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
       "requires": {
         "prelude-ls": "1.1.2",
         "type-check": "0.3.2"
       }
     },
     "lodash": {
-      "version": "4.17.5",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
-      "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
+      "version": "4.17.10",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+      "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
     },
     "lru-cache": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz",
       "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==",
       "requires": {
         "pseudomap": "1.0.2",
         "yallist": "2.1.2"
@@ -849,17 +859,17 @@
       "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
       "requires": {
         "pinkie": "2.0.4"
       }
     },
     "pluralize": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
-      "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c="
+      "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow=="
     },
     "prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
       "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
     },
     "process-nextick-args": {
       "version": "2.0.0",
@@ -872,29 +882,34 @@
       "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8="
     },
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
       "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
     },
     "readable-stream": {
-      "version": "2.3.5",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz",
-      "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==",
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
       "requires": {
         "core-util-is": "1.0.2",
         "inherits": "2.0.3",
         "isarray": "1.0.0",
         "process-nextick-args": "2.0.0",
-        "safe-buffer": "5.1.1",
-        "string_decoder": "1.0.3",
+        "safe-buffer": "5.1.2",
+        "string_decoder": "1.1.1",
         "util-deprecate": "1.0.2"
       }
     },
+    "regexpp": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
+      "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw=="
+    },
     "require-uncached": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
       "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
       "requires": {
         "caller-path": "0.1.0",
         "resolve-from": "1.0.1"
       }
@@ -911,17 +926,17 @@
       "requires": {
         "onetime": "2.0.1",
         "signal-exit": "3.0.2"
       }
     },
     "rimraf": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-      "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=",
+      "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
       "requires": {
         "glob": "7.1.2"
       }
     },
     "run-async": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
       "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
@@ -938,29 +953,34 @@
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
       "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
       "requires": {
         "rx-lite": "4.0.8"
       }
     },
     "safe-buffer": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-      "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
     },
     "semver": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
-      "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs="
+      "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
     },
     "shebang-command": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
       "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
       "requires": {
         "shebang-regex": "1.0.0"
       }
@@ -993,21 +1013,21 @@
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
       "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
       "requires": {
         "is-fullwidth-code-point": "2.0.0",
         "strip-ansi": "4.0.0"
       }
     },
     "string_decoder": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
       "requires": {
-        "safe-buffer": "5.1.1"
+        "safe-buffer": "5.1.2"
       }
     },
     "strip-ansi": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
       "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
       "requires": {
         "ansi-regex": "3.0.0"
@@ -1032,18 +1052,18 @@
     },
     "table": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
       "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
       "requires": {
         "ajv": "5.5.2",
         "ajv-keywords": "2.1.1",
-        "chalk": "2.3.2",
-        "lodash": "4.17.5",
+        "chalk": "2.4.1",
+        "lodash": "4.17.10",
         "slice-ansi": "1.0.0",
         "string-width": "2.1.1"
       }
     },
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
       "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
@@ -1051,17 +1071,17 @@
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
     },
     "tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-      "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
       "requires": {
         "os-tmpdir": "1.0.2"
       }
     },
     "type-check": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
       "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
@@ -1077,17 +1097,17 @@
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
     },
     "which": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
-      "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=",
+      "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
       "requires": {
         "isexe": "2.0.0"
       }
     },
     "wordwrap": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
       "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
--- a/package.json
+++ b/package.json
@@ -1,15 +1,15 @@
 {
   "name": "mozillaeslintsetup",
   "description": "This package file is for setup of ESLint only for editor integration.",
   "repository": {},
   "license": "MPL-2.0",
   "dependencies": {
-    "eslint": "4.18.2",
-    "eslint-plugin-html": "4.0.2",
+    "eslint": "4.19.1",
+    "eslint-plugin-html": "4.0.3",
     "eslint-plugin-mozilla": "file:tools/lint/eslint/eslint-plugin-mozilla",
     "eslint-plugin-no-unsanitized": "3.0.0",
     "eslint-plugin-react": "7.1.0",
     "eslint-plugin-spidermonkey-js": "file:tools/lint/eslint/eslint-plugin-spidermonkey-js"
   },
   "devDependencies": {}
 }
--- a/taskcluster/docker/firefox-snap/snapcraft.yaml.in
+++ b/taskcluster/docker/firefox-snap/snapcraft.yaml.in
@@ -32,16 +32,18 @@ apps:
       - x11
 
 plugs:
   browser-sandbox:
     interface: browser-support
     allow-sandbox: true
 
 parts:
+  desktop-gtk3:
+    source-commit: 6a600b00773e8e4624aa12ee1f8e013ba9f2fc03
   firefox:
     plugin: dump
     source: source
     stage-packages:
       - libxt6
       - libdbus-glib-1-2
       - libasound2
       - libpulse0
--- a/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
+++ b/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
@@ -1,10 +1,10 @@
 [
   {
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "eslint-plugin-mozilla.tar.gz",
     "unpack": true,
-    "digest": "0a2fcb783b8761b2054f25e6621849d55427fc3349e22c346ce56d4969928e72368ac7fe58536d7801f89d60146fa9a58493c20f9cf376b2563fd32cc7b701c4",
-    "size": 3221147
+    "digest": "23efd573e70338dc11b33e3fbee5c258af376517e20bf80905beee4465e0e51ac41737617d21589407e5bce9bba39756195f147aca970be1d04bfdb016a3bbdd",
+    "size": 3347884
   }
 ]
--- a/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json
@@ -41,19 +41,19 @@
     },
     "ajv-keywords": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
       "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
       "dev": true
     },
     "ansi-escapes": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
-      "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
+      "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
       "dev": true
     },
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
       "dev": true
     },
@@ -145,55 +145,61 @@
       }
     },
     "browser-stdout": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
       "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
       "dev": true
     },
+    "buffer-from": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
+      "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==",
+      "dev": true
+    },
     "caller-path": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
       "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
       "dev": true,
       "requires": {
         "callsites": "0.2.0"
       }
     },
     "callsites": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
       "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
       "dev": true
     },
     "chalk": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
-      "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz",
+      "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==",
       "dev": true,
       "requires": {
         "ansi-styles": "3.2.1",
         "escape-string-regexp": "1.0.5",
-        "supports-color": "5.3.0"
+        "supports-color": "5.4.0"
       },
       "dependencies": {
         "ansi-styles": {
           "version": "3.2.1",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
           "dev": true,
           "requires": {
             "color-convert": "1.9.1"
           }
         },
         "supports-color": {
-          "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
-          "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
+          "version": "5.4.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+          "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
           "dev": true,
           "requires": {
             "has-flag": "3.0.0"
           }
         }
       }
     },
     "chardet": {
@@ -252,23 +258,24 @@
     },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "dev": true
     },
     "concat-stream": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.1.tgz",
-      "integrity": "sha512-gslSSJx03QKa59cIKqeJO9HQ/WZMotvYJCuaUULrLpjj8oG40kV2Z+gz82pVxlTkOADi4PJxQPPfhl1ELYrrXw==",
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
       "dev": true,
       "requires": {
+        "buffer-from": "1.0.0",
         "inherits": "2.0.3",
-        "readable-stream": "2.3.5",
+        "readable-stream": "2.3.6",
         "typedarray": "0.0.6"
       }
     },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
       "dev": true
@@ -302,17 +309,17 @@
     "del": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
       "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
       "dev": true,
       "requires": {
         "globby": "5.0.0",
         "is-path-cwd": "1.0.0",
-        "is-path-in-cwd": "1.0.0",
+        "is-path-in-cwd": "1.0.1",
         "object-assign": "4.1.1",
         "pify": "2.3.0",
         "pinkie-promise": "2.0.1",
         "rimraf": "2.6.2"
       }
     },
     "diff": {
       "version": "3.5.0",
@@ -331,52 +338,53 @@
     },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
       "dev": true
     },
     "eslint": {
-      "version": "4.18.2",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.2.tgz",
-      "integrity": "sha512-qy4i3wODqKMYfz9LUI8N2qYDkHkoieTbiHpMrYUI/WbjhXJQr7lI4VngixTgaG+yHX+NBCv7nW4hA0ShbvaNKw==",
+      "version": "4.19.1",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
+      "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
       "dev": true,
       "requires": {
         "ajv": "5.5.2",
         "babel-code-frame": "6.26.0",
-        "chalk": "2.3.2",
-        "concat-stream": "1.6.1",
+        "chalk": "2.4.0",
+        "concat-stream": "1.6.2",
         "cross-spawn": "5.1.0",
         "debug": "3.1.0",
         "doctrine": "2.1.0",
         "eslint-scope": "3.7.1",
         "eslint-visitor-keys": "1.0.0",
         "espree": "3.5.4",
-        "esquery": "1.0.0",
+        "esquery": "1.0.1",
         "esutils": "2.0.2",
         "file-entry-cache": "2.0.0",
         "functional-red-black-tree": "1.0.1",
         "glob": "7.1.2",
-        "globals": "11.3.0",
+        "globals": "11.4.0",
         "ignore": "3.3.7",
         "imurmurhash": "0.1.4",
         "inquirer": "3.3.0",
         "is-resolvable": "1.1.0",
         "js-yaml": "3.11.0",
         "json-stable-stringify-without-jsonify": "1.0.1",
         "levn": "0.3.0",
         "lodash": "4.17.5",
         "minimatch": "3.0.4",
         "mkdirp": "0.5.1",
         "natural-compare": "1.4.0",
         "optionator": "0.8.2",
         "path-is-inside": "1.0.2",
         "pluralize": "7.0.0",
         "progress": "2.0.0",
+        "regexpp": "1.1.0",
         "require-uncached": "1.0.3",
         "semver": "5.5.0",
         "strip-ansi": "4.0.0",
         "strip-json-comments": "2.0.1",
         "table": "4.0.2",
         "text-table": "0.2.0"
       }
     },
@@ -408,19 +416,19 @@
     },
     "esprima": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
       "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
       "dev": true
     },
     "esquery": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
-      "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+      "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
       "dev": true,
       "requires": {
         "estraverse": "4.2.0"
       }
     },
     "esrecurse": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
@@ -438,23 +446,23 @@
     },
     "esutils": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
       "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
       "dev": true
     },
     "external-editor": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz",
-      "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+      "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
       "dev": true,
       "requires": {
         "chardet": "0.4.2",
-        "iconv-lite": "0.4.19",
+        "iconv-lite": "0.4.21",
         "tmp": "0.0.33"
       }
     },
     "fast-deep-equal": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
       "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
       "dev": true
@@ -524,19 +532,19 @@
         "inflight": "1.0.6",
         "inherits": "2.0.3",
         "minimatch": "3.0.4",
         "once": "1.4.0",
         "path-is-absolute": "1.0.1"
       }
     },
     "globals": {
-      "version": "11.3.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz",
-      "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==",
+      "version": "11.4.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.4.0.tgz",
+      "integrity": "sha512-Dyzmifil8n/TmSqYDEXbm+C8yitzJQqQIlJQLNRMwa+BOUJpRC19pyVeN12JAjt61xonvXjtff+hJruTRXn5HA==",
       "dev": true
     },
     "globby": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
       "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
       "dev": true,
       "requires": {
@@ -577,20 +585,23 @@
     },
     "he": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
       "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
       "dev": true
     },
     "iconv-lite": {
-      "version": "0.4.19",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-      "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
-      "dev": true
+      "version": "0.4.21",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz",
+      "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": "2.1.2"
+      }
     },
     "ignore": {
       "version": "3.3.7",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
       "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==",
       "dev": true
     },
     "imurmurhash": {
@@ -621,21 +632,21 @@
       "integrity": "sha1-+kF4flZ3Y7P/Zdel2alO23QHh+8="
     },
     "inquirer": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
       "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
       "dev": true,
       "requires": {
-        "ansi-escapes": "3.0.0",
-        "chalk": "2.3.2",
+        "ansi-escapes": "3.1.0",
+        "chalk": "2.4.0",
         "cli-cursor": "2.1.0",
         "cli-width": "2.2.0",
-        "external-editor": "2.1.0",
+        "external-editor": "2.2.0",
         "figures": "2.0.0",
         "lodash": "4.17.5",
         "mute-stream": "0.0.7",
         "run-async": "2.3.0",
         "rx-lite": "4.0.8",
         "rx-lite-aggregates": "4.0.8",
         "string-width": "2.1.1",
         "strip-ansi": "4.0.0",
@@ -650,19 +661,19 @@
     },
     "is-path-cwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
       "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
       "dev": true
     },
     "is-path-in-cwd": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
-      "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
+      "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
       "dev": true,
       "requires": {
         "is-path-inside": "1.0.1"
       }
     },
     "is-path-inside": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
@@ -776,29 +787,30 @@
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "dev": true,
       "requires": {
         "minimist": "0.0.8"
       }
     },
     "mocha": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.4.tgz",
-      "integrity": "sha512-nMOpAPFosU1B4Ix1jdhx5e3q7XO55ic5a8cgYvW27CequcEY+BabS0kUVL1Cw1V5PuVHZWeNRWFLmEPexo79VA==",
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz",
+      "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==",
       "dev": true,
       "requires": {
         "browser-stdout": "1.3.1",
         "commander": "2.11.0",
         "debug": "3.1.0",
         "diff": "3.5.0",
         "escape-string-regexp": "1.0.5",
         "glob": "7.1.2",
         "growl": "1.10.3",
         "he": "1.1.1",
+        "minimatch": "3.0.4",
         "mkdirp": "0.5.1",
         "supports-color": "4.4.0"
       },
       "dependencies": {
         "has-flag": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
           "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
@@ -936,30 +948,36 @@
     },
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
       "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
       "dev": true
     },
     "readable-stream": {
-      "version": "2.3.5",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz",
-      "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==",
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
       "dev": true,
       "requires": {
         "core-util-is": "1.0.2",
         "inherits": "2.0.3",
         "isarray": "1.0.0",
         "process-nextick-args": "2.0.0",
         "safe-buffer": "5.1.1",
-        "string_decoder": "1.0.3",
+        "string_decoder": "1.1.1",
         "util-deprecate": "1.0.2"
       }
     },
+    "regexpp": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
+      "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==",
+      "dev": true
+    },
     "require-uncached": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
       "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
       "dev": true,
       "requires": {
         "caller-path": "0.1.0",
         "resolve-from": "1.0.1"
@@ -1015,16 +1033,22 @@
       }
     },
     "safe-buffer": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
       "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
       "dev": true
     },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
     },
     "semver": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@@ -1073,19 +1097,19 @@
       "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
       "dev": true,
       "requires": {
         "is-fullwidth-code-point": "2.0.0",
         "strip-ansi": "4.0.0"
       }
     },
     "string_decoder": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
       "dev": true,
       "requires": {
         "safe-buffer": "5.1.1"
       }
     },
     "strip-ansi": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@@ -1118,17 +1142,17 @@
     "table": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
       "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
       "dev": true,
       "requires": {
         "ajv": "5.5.2",
         "ajv-keywords": "2.1.1",
-        "chalk": "2.3.2",
+        "chalk": "2.4.0",
         "lodash": "4.17.5",
         "slice-ansi": "1.0.0",
         "string-width": "2.1.1"
       }
     },
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
--- a/tools/lint/eslint/eslint-plugin-mozilla/package.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -19,18 +19,18 @@
   },
   "author": "Mike Ratcliffe",
   "main": "lib/index.js",
   "dependencies": {
     "ini-parser": "0.0.2",
     "sax": "1.2.4"
   },
   "devDependencies": {
-    "eslint": "4.18.2",
-    "mocha": "5.0.4"
+    "eslint": "4.19.1",
+    "mocha": "5.1.1"
   },
   "peerDependencies": {
     "eslint": "^4.0.0",
     "eslint-plugin-no-unsanitized": "^3.0.0"
   },
   "engines": {
     "node": ">=6.9.1"
   },
--- a/tools/lint/eslint/manifest.tt
+++ b/tools/lint/eslint/manifest.tt
@@ -1,10 +1,10 @@
 [
   {
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "eslint.tar.gz",
     "unpack": true,
-    "digest": "75cf4d907c146fbbc3010fe322e06742090726ef9c4337fc870231b281afa88b50d807f37fd09b341ea2dbcc3b77f5d6b37924f4efa3264c7a55cda95d31a3bb",
-    "size": 2964047
+    "digest": "18340ca8801e23514ccf1626cda0b5e54dba5fdca235324f0accd557fa669b81fe8ad6cf4eb80d09eb4a6cf59b97109ccd7fb0382b7b852f545ac4e3f88b96ef",
+    "size": 3109266
   }
 ]
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -3117,77 +3117,65 @@ GetActiveScrollbarMetrics(GtkOrientation
 
 /*
  * get_shadow_width() from gtkwindow.c is not public so we need
  * to implement it.
  */
 bool
 GetCSDDecorationSize(GtkWindow *aGtkWindow, GtkBorder* aDecorationSize)
 {
+    // Available on GTK 3.20+.
+    static auto sGtkRenderBackgroundGetClip =
+        (void (*)(GtkStyleContext*, gdouble, gdouble, gdouble, gdouble, GdkRectangle*))
+        dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip");
+
+    if (!sGtkRenderBackgroundGetClip) {
+        *aDecorationSize = {0,0,0,0};
+        return false;
+    }
+
     GtkStyleContext* context = gtk_widget_get_style_context(GTK_WIDGET(aGtkWindow));
     bool solidDecorations = gtk_style_context_has_class(context, "solid-csd");
     context = GetStyleContext(solidDecorations ?
                               MOZ_GTK_WINDOW_DECORATION_SOLID :
                               MOZ_GTK_WINDOW_DECORATION);
 
     /* Always sum border + padding */
     GtkBorder padding;
     GtkStateFlags state = gtk_style_context_get_state(context);
     gtk_style_context_get_border(context, state, aDecorationSize);
     gtk_style_context_get_padding(context, state, &padding);
     *aDecorationSize += padding;
 
-    // Available on GTK 3.20+.
-    static auto sGtkRenderBackgroundGetClip =
-        (void (*)(GtkStyleContext*, gdouble, gdouble, gdouble, gdouble, GdkRectangle*))
-        dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip");
 
     GtkBorder margin;
     gtk_style_context_get_margin(context, state, &margin);
 
-    GtkBorder extents = {0, 0, 0, 0};
-    if (sGtkRenderBackgroundGetClip) {
-        /* Get shadow extents but combine with style margin; use the bigger value.
-         */
-        GdkRectangle clip;
-        sGtkRenderBackgroundGetClip(context, 0, 0, 0, 0, &clip);
-
-        extents.top = -clip.y;
-        extents.right = clip.width + clip.x;
-        extents.bottom = clip.height + clip.y;
-        extents.left = -clip.x;
-
-        // Margin is used for resize grip size - it's not present on
-        // popup windows.
-        if (gtk_window_get_window_type(aGtkWindow) != GTK_WINDOW_POPUP) {
-            extents.top = MAX(extents.top, margin.top);
-            extents.right = MAX(extents.right, margin.right);
-            extents.bottom = MAX(extents.bottom, margin.bottom);
-            extents.left = MAX(extents.left, margin.left);
-        }
-    } else {
-        /* If we can't get shadow extents use decoration-resize-handle instead
-         * as a workaround. This is inspired by update_border_windows()
-         * from gtkwindow.c although this is not 100% accurate as we emulate
-         * the extents here.
-         */
-        gint handle;
-        gtk_widget_style_get(GetWidget(MOZ_GTK_WINDOW),
-                             "decoration-resize-handle", &handle,
-                             NULL);
-
-        extents.top = handle + margin.top;
-        extents.right = handle + margin.right;
-        extents.bottom = handle + margin.bottom;
-        extents.left = handle + margin.left;
+    /* Get shadow extents but combine with style margin; use the bigger value.
+     */
+    GdkRectangle clip;
+    sGtkRenderBackgroundGetClip(context, 0, 0, 0, 0, &clip);
+
+    GtkBorder extents;
+    extents.top = -clip.y;
+    extents.right = clip.width + clip.x;
+    extents.bottom = clip.height + clip.y;
+    extents.left = -clip.x;
+
+    // Margin is used for resize grip size - it's not present on
+    // popup windows.
+    if (gtk_window_get_window_type(aGtkWindow) != GTK_WINDOW_POPUP) {
+        extents.top = MAX(extents.top, margin.top);
+        extents.right = MAX(extents.right, margin.right);
+        extents.bottom = MAX(extents.bottom, margin.bottom);
+        extents.left = MAX(extents.left, margin.left);
     }
 
     *aDecorationSize += extents;
-
-    return (sGtkRenderBackgroundGetClip != nullptr);
+    return true;
 }
 
 /* cairo_t *cr argument has to be a system-cairo. */
 gint
 moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr,
                      GdkRectangle* rect,
                      GtkWidgetState* state, gint flags,
                      GtkTextDirection direction)
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -3391,16 +3391,20 @@ nsWindow::OnWindowStateEvent(GtkWidget *
 
     if (mWidgetListener) {
       mWidgetListener->SizeModeChanged(mSizeState);
       if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
         mWidgetListener->FullscreenChanged(
           aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN);
       }
     }
+
+    if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+        UpdateClientOffsetForCSDWindow();
+    }
 }
 
 void
 nsWindow::ThemeChanged()
 {
     NotifyThemeChanged();
 
     if (!mGdkWindow || MOZ_UNLIKELY(mIsDestroyed))
@@ -6630,19 +6634,23 @@ nsWindow::ClearCachedResources()
  * It works only for CSD decorated GtkWindow.
  */
 void
 nsWindow::UpdateClientOffsetForCSDWindow()
 {
     // _NET_FRAME_EXTENTS is not set on client decorated windows,
     // so we need to read offset between mContainer and toplevel mShell
     // window.
-    GtkBorder decorationSize;
-    GetCSDDecorationSize(GTK_WINDOW(mShell), &decorationSize);
-    mClientOffset = nsIntPoint(decorationSize.left, decorationSize.top);
+    if (mSizeState == nsSizeMode_Normal) {
+        GtkBorder decorationSize;
+        GetCSDDecorationSize(GTK_WINDOW(mShell), &decorationSize);
+        mClientOffset = nsIntPoint(decorationSize.left, decorationSize.top);
+    } else {
+        mClientOffset = nsIntPoint(0, 0);
+    }
 
     // Send a WindowMoved notification. This ensures that TabParent
     // picks up the new client offset and sends it to the child process
     // if appropriate.
     NotifyWindowMoved(mBounds.x, mBounds.y);
 }
 
 nsresult
@@ -6714,16 +6722,21 @@ nsWindow::SetDrawsInTitlebar(bool aState
                                         &allocation.height);
         gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
 
         gtk_widget_realize(GTK_WIDGET(mShell));
         gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
         mNeedsShow = true;
         NativeResize();
 
+        // Label mShell toplevel window so property_notify_event_cb callback
+        // can find its way home.
+        g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)),
+                          "nsWindow", this);
+
         UpdateClientOffsetForCSDWindow();
 
         gtk_widget_destroy(tmpWindow);
     }
 
     mDrawInTitlebar = aState;
 }