Merge fx-team to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 27 May 2016 14:41:36 -0700
changeset 338301 ea15028498ed95677844fb7f30be5efcaf8b2621
parent 338286 33c4562b47ce2cc5e2988d756c76fa79a36c469a (current diff)
parent 338300 dd3e899cfa818fcbb47b3d7432e0977e835702ee (diff)
child 338302 c6faaea6caf4232f23821f8c453a4e35f564801f
child 338311 b094e88c2184607005c0ce50b6eca797bc50de0a
child 338405 5c73a69cb9eb5aab005d31d601f69abaa792390a
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.0a1
first release with
nightly linux32
ea15028498ed / 49.0a1 / 20160528030208 / files
nightly linux64
ea15028498ed / 49.0a1 / 20160528030208 / files
nightly mac
ea15028498ed / 49.0a1 / 20160528030208 / files
nightly win32
ea15028498ed / 49.0a1 / 20160528030208 / files
nightly win64
ea15028498ed / 49.0a1 / 20160528030208 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to central, a=merge
browser/app/profile/firefox.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -364,16 +364,18 @@ pref("browser.search.context.loadInBackg
 pref("browser.search.hiddenOneOffs", "");
 
 #ifdef XP_WIN
 pref("browser.search.redirectWindowsSearch", true);
 #else
 pref("browser.search.redirectWindowsSearch", false);
 #endif
 
+pref("browser.search.reset.enabled", true);
+
 pref("browser.usedOnWindows10", false);
 pref("browser.usedOnWindows10.introURL", "https://www.mozilla.org/%LOCALE%/firefox/windows-10/welcome/?utm_source=firefox-browser&utm_medium=firefox-browser");
 
 pref("browser.sessionhistory.max_entries", 50);
 
 // Built-in default permissions.
 pref("permissions.manager.defaultsUrl", "resource://app/defaults/permissions");
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7047,17 +7047,17 @@ var gIdentityHandler = {
 
     try {
       this._uri.host;
       this._uriHasHost = true;
     } catch (ex) {
       this._uriHasHost = false;
     }
 
-    let whitelist = /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
+    let whitelist = /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
     this._isSecureInternalUI = uri.schemeIs("about") && whitelist.test(uri.path);
 
     // Create a channel for the sole purpose of getting the resolved URI
     // of the request to determine if it's loaded from the file system.
     this._isURILoadedFromFile = false;
     let chanOptions = {uri: this._uri, loadUsingSystemPrincipal: true};
     let resolvedURI;
     try {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -735,51 +735,51 @@
                      ontextentered="this.handleCommand(param);"
                      ontextreverted="return this.handleRevert();"
                      pageproxystate="invalid"
                      onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
                      onblur="setTimeout(() => { document.getElementById('identity-box').style.MozUserFocus = ''; }, 0);">
               <box id="notification-popup-box" hidden="true" align="center">
                 <image id="default-notification-icon" class="notification-anchor-icon" role="button"
                        aria-label="&urlbar.defaultNotificationAnchor.label;"/>
-                <image id="geo-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="geo-notification-icon" class="notification-anchor-icon geo-icon" role="button"
                        aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
-                <image id="addons-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"
                        aria-label="&urlbar.addonsNotificationAnchor.label;"/>
-                <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button"
                        aria-label="&urlbar.indexedDBNotificationAnchor.label;"/>
-                <image id="login-fill-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="login-fill-notification-icon" class="notification-anchor-icon login-icon" role="button"
                        aria-label="&urlbar.loginFillNotificationAnchor.label;"/>
-                <image id="password-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
                        aria-label="&urlbar.passwordNotificationAnchor.label;"/>
-                <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="plugins-notification-icon" class="notification-anchor-icon plugin-icon" role="button"
                        aria-label="&urlbar.pluginsNotificationAnchor.label;"/>
-                <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="web-notifications-notification-icon" class="notification-anchor-icon web-notifications-icon" role="button"
                        aria-label="&urlbar.webNotsNotificationAnchor3.label;"/>
-                <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon camera-icon" role="button"
                        aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
-                <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon camera-icon in-use" role="button"
                        aria-label="&urlbar.webRTCSharingDevicesNotificationAnchor.label;"/>
-                <image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon microphone-icon" role="button"
                        aria-label="&urlbar.webRTCShareMicrophoneNotificationAnchor.label;"/>
-                <image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon microphone-icon in-use" role="button"
                        aria-label="&urlbar.webRTCSharingMicrophoneNotificationAnchor.label;"/>
-                <image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon screen-icon" role="button"
                        aria-label="&urlbar.webRTCShareScreenNotificationAnchor.label;"/>
-                <image id="webRTC-sharingScreen-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="webRTC-sharingScreen-notification-icon" class="notification-anchor-icon screen-icon in-use" role="button"
                        aria-label="&urlbar.webRTCSharingScreenNotificationAnchor.label;"/>
-                <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="pointerLock-notification-icon" class="notification-anchor-icon pointer-icon" role="button"
                        aria-label="&urlbar.pointerLockNotificationAnchor.label;"/>
-                <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="servicesInstall-notification-icon" class="notification-anchor-icon service-icon" role="button"
                        aria-label="&urlbar.servicesNotificationAnchor.label;"/>
-                <image id="translate-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="translate-notification-icon" class="notification-anchor-icon translation-icon" role="button"
                        aria-label="&urlbar.translateNotificationAnchor.label;"/>
-                <image id="translated-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="translated-notification-icon" class="notification-anchor-icon translation-icon in-use" role="button"
                        aria-label="&urlbar.translatedNotificationAnchor.label;"/>
-                <image id="eme-notification-icon" class="notification-anchor-icon" role="button"
+                <image id="eme-notification-icon" class="notification-anchor-icon drm-icon" role="button"
                        aria-label="&urlbar.emeNotificationAnchor.label;"/>
               </box>
               <!-- Use onclick instead of normal popup= syntax since the popup
                    code fires onmousedown, and hence eats our favicon drag events.
                    We only add the identity-box button to the tab order when the location bar
                    has focus, otherwise pressing F6 focuses it instead of the location bar -->
               <box id="identity-box" role="button"
                    align="center"
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -8,20 +8,20 @@
   <binding id="chatbox">
     <content orient="vertical" mousethrough="never">
       <xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected,activity" align="baseline">
         <xul:hbox flex="1" onclick="document.getBindingParent(this).onTitlebarClick(event);">
           <xul:image class="chat-status-icon" xbl:inherits="src=image"/>
           <xul:label class="chat-title" flex="1" xbl:inherits="crop=titlecrop,value=label" crop="end"/>
         </xul:hbox>
         <xul:toolbarbutton anonid="webRTC-shareScreen-icon"
-                           class="notification-anchor-icon chat-toolbarbutton"
+                           class="notification-anchor-icon chat-toolbarbutton screen-icon"
                            oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
         <xul:toolbarbutton anonid="webRTC-sharingScreen-icon"
-                           class="notification-anchor-icon chat-toolbarbutton"
+                           class="notification-anchor-icon chat-toolbarbutton screen-icon in-use"
                            oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
         <xul:toolbarbutton anonid="notification-icon" class="notification-anchor-icon chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
         <xul:toolbarbutton anonid="minimize" class="chat-minimize-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).toggle();"/>
         <xul:toolbarbutton anonid="swap" class="chat-swap-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).swapWindows();"/>
         <xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -191,17 +191,17 @@ function checkPopup(popup, notifyObj) {
   is(notifications.length, 1, "one notification displayed");
   let notification = notifications[0];
   if (!notification)
     return;
   let icon = document.getAnonymousElementByAttribute(notification, "class",
                                                      "popup-notification-icon");
   if (notifyObj.id == "geolocation") {
     isnot(icon.boxObject.width, 0, "icon for geo displayed");
-    is(popup.anchorNode.className, "notification-anchor-icon",
+    ok(popup.anchorNode.classList.contains("notification-anchor-icon"),
        "notification anchored to icon");
   }
   is(notification.getAttribute("label"), notifyObj.message, "message matches");
   is(notification.id, notifyObj.id + "-notification", "id matches");
   if (notifyObj.mainAction) {
     is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
        "main action label matches");
     is(notification.getAttribute("buttonaccesskey"),
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -68,16 +68,19 @@ static RedirEntry kRedirMap[] = {
     nsIAboutModule::ALLOW_SCRIPT },
   { "rights",
     "chrome://global/content/aboutRights.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
   { "robots", "chrome://browser/content/aboutRobots.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT },
+  { "searchreset", "chrome://browser/content/search/searchReset.xhtml",
+    nsIAboutModule::ALLOW_SCRIPT |
+    nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
   { "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
   { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
     nsIAboutModule::ALLOW_SCRIPT },
   // Linkable because of indexeddb use (bug 1228118)
   { "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -91,16 +91,17 @@ static const mozilla::Module::ContractID
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "providerdirectory", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+    { NS_ABOUT_MODULE_CONTRACTID_PREFIX "searchreset", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "welcomeback", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-tabs", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -108,17 +108,17 @@
         <description class="identity-popup-connection-secure"
                      value="&identity.connectionSecure;"
                      when-connection="secure secure-ev"/>
       </vbox>
 
       <vbox id="identity-popup-securityView-body" flex="1">
         <!-- (EV) Certificate Information -->
         <description id="identity-popup-content-verified-by"
-                     when-connection="secure-ev">&identity.connectionVerified1;</description>
+                     when-connection="secure-ev">&identity.connectionVerified2;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
         <description id="identity-popup-content-verifier"
                      when-connection="secure secure-ev secure-cert-user-overridden"/>
 
new file mode 100644
--- /dev/null
+++ b/browser/components/search/content/searchReset.js
@@ -0,0 +1,111 @@
+/* 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";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const TELEMETRY_RESULT_ENUM = {
+  RESTORED_DEFAULT: 0,
+  KEPT_CURRENT: 1,
+  CHANGED_ENGINE: 2,
+  CLOSED_PAGE: 3
+};
+
+window.onload = function() {
+  let list = document.getElementById("defaultEngine");
+  let originalDefault = Services.search.originalDefaultEngine.name;
+  Services.search.getDefaultEngines().forEach(e => {
+    let opt = document.createElement("option");
+    opt.setAttribute("value", e.name);
+    opt.engine = e;
+    opt.textContent = e.name;
+    if (e.iconURI)
+      opt.style.backgroundImage = 'url("' + e.iconURI.spec + '")';
+    if (e.name == originalDefault)
+      opt.setAttribute("selected", "true");
+    list.appendChild(opt);
+  });
+
+  let updateIcon = () => {
+    list.style.setProperty("--engine-icon-url",
+                           list.selectedOptions[0].style.backgroundImage);
+  };
+
+  list.addEventListener("change", updateIcon);
+  // When selecting using the keyboard, the 'change' event is only fired after
+  // the user presses <enter> or moves the focus elsewhere.
+  // keypress/keyup fire too late and cause flicker when updating the icon.
+  // keydown fires too early and the selected option isn't changed yet.
+  list.addEventListener("keydown", () => {
+    Services.tm.mainThread.dispatch(updateIcon, Ci.nsIThread.DISPATCH_NORMAL);
+  });
+  updateIcon();
+
+  document.getElementById("searchResetChangeEngine").focus();
+  window.addEventListener("unload", recordPageClosed);
+};
+
+function doSearch() {
+  let queryString = "";
+  let purpose = "";
+  let params = window.location.href.match(/^about:searchreset\?([^#]*)/);
+  if (params) {
+    params = params[1].split("&");
+    for (let param of params) {
+      if (param.startsWith("data="))
+        queryString = decodeURIComponent(param.slice(5));
+      else if (param.startsWith("purpose="))
+        purpose = param.slice(8);
+    }
+  }
+
+  let engine = Services.search.currentEngine;
+  let submission = engine.getSubmission(queryString, null, purpose);
+
+  window.removeEventListener("unload", recordPageClosed);
+
+  let win = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIWebNavigation)
+                  .QueryInterface(Ci.nsIDocShellTreeItem)
+                  .rootTreeItem
+                  .QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIDOMWindow);
+  win.openUILinkIn(submission.uri.spec, "current", false, submission.postData);
+}
+
+function record(result) {
+  Services.telemetry.getHistogramById("SEARCH_RESET_RESULT").add(result);
+}
+
+function keepCurrentEngine() {
+  // Calling the currentEngine setter will force a correct loadPathHash to be
+  // written for this engine, so that we don't prompt the user again.
+  Services.search.currentEngine = Services.search.currentEngine;
+  record(TELEMETRY_RESULT_ENUM.KEPT_CURRENT);
+  doSearch();
+}
+
+function changeSearchEngine() {
+  let list = document.getElementById("defaultEngine");
+  let engine = list.selectedOptions[0].engine;
+  if (engine.hidden)
+    engine.hidden = false;
+  Services.search.currentEngine = engine;
+
+  // Record if we restored the original default or changed to another engine.
+  let originalDefault = Services.search.originalDefaultEngine.name;
+  let code = TELEMETRY_RESULT_ENUM.CHANGED_ENGINE;
+  if (Services.search.originalDefaultEngine.name == engine.name)
+    code = TELEMETRY_RESULT_ENUM.RESTORED_DEFAULT;
+  record(code);
+
+  doSearch();
+}
+
+function recordPageClosed() {
+  record(TELEMETRY_RESULT_ENUM.CLOSED_PAGE);
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/search/content/searchReset.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+  %globalDTD;
+  <!ENTITY % searchresetDTD SYSTEM "chrome://browser/locale/aboutSearchReset.dtd">
+  %searchresetDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <head>
+    <title>&searchreset.tabtitle;</title>
+    <link rel="stylesheet" type="text/css" media="all"
+          href="chrome://global/skin/in-content/info-pages.css"/>
+    <link rel="stylesheet" type="text/css" media="all"
+          href="chrome://browser/skin/searchReset.css"/>
+    <link rel="icon" type="image/png"
+          href="chrome://browser/skin/favicon-search-16.svg"/>
+
+    <script type="application/javascript;version=1.8"
+            src="chrome://browser/content/search/searchReset.js"/>
+  </head>
+
+  <body dir="&locale.dir;">
+
+    <div class="container">
+      <div class="title">
+        <h1 class="title-text">&searchreset.pageTitle;</h1>
+      </div>
+
+      <div class="description">
+        <p>&searchreset.pageInfo1;</p>
+        <p>&searchreset.selector.label;
+          <select id="defaultEngine"></select>
+        </p>
+
+        <p>&searchreset.beforelink.pageInfo2;<a id="linkSettingsPage" href="about:preferences#search">&searchreset.link.pageInfo2;</a>&searchreset.afterlink.pageInfo2;</p>
+      </div>
+
+      <div class="button-container">
+        <xul:button id="searchResetKeepCurrent"
+                    label="&searchreset.noChangeButton;"
+                    accesskey="&searchreset.noChangeButton.access;"
+                    oncommand="keepCurrentEngine();"/>
+        <xul:button class="primary"
+                    id="searchResetChangeEngine"
+                    label="&searchreset.changeEngineButton;"
+                    accesskey="&searchreset.changeEngineButton.access;"
+                    oncommand="changeSearchEngine();"/>
+      </div>
+    </div>
+
+  </body>
+</html>
--- a/browser/components/search/jar.mn
+++ b/browser/components/search/jar.mn
@@ -1,7 +1,9 @@
 # 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/.
 
 browser.jar:
         content/browser/search/search.xml                           (content/search.xml)
         content/browser/search/searchbarBindings.css                (content/searchbarBindings.css)
+        content/browser/search/searchReset.xhtml                    (content/searchReset.xhtml)
+        content/browser/search/searchReset.js                       (content/searchReset.js)
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -32,13 +32,14 @@ skip-if = os == "mac" # bug 967013
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffHeader.js]
 [browser_private_search_perwindowpb.js]
 [browser_yahoo.js]
 [browser_yahoo_behavior.js]
 [browser_abouthome_behavior.js]
 skip-if = true # Bug ??????, Bug 1100301 - leaks windows until shutdown when --run-by-dir
+[browser_aboutSearchReset.js]
 [browser_searchbar_openpopup.js]
 skip-if = os == "linux" # Linux has different focus behaviours.
 [browser_searchbar_keyboard_navigation.js]
 [browser_searchbar_smallpanel_keyboard_navigation.js]
 [browser_webapi.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/browser_aboutSearchReset.js
@@ -0,0 +1,181 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TELEMETRY_RESULT_ENUM = {
+  RESTORED_DEFAULT: 0,
+  KEPT_CURRENT: 1,
+  CHANGED_ENGINE: 2,
+  CLOSED_PAGE: 3
+};
+
+const kSearchStr = "a search";
+const kSearchPurpose = "searchbar";
+
+const kTestEngine = "testEngine.xml";
+
+function checkTelemetryRecords(expectedValue) {
+  let histogram = Services.telemetry.getHistogramById("SEARCH_RESET_RESULT");
+  let snapshot = histogram.snapshot();
+  // The probe is declared with 5 values, but we get 6 back from .counts
+  let expectedCounts = [0, 0, 0, 0, 0, 0];
+  if (expectedValue != null) {
+    expectedCounts[expectedValue] = 1;
+  }
+  Assert.deepEqual(snapshot.counts, expectedCounts,
+                   "histogram has expected content");
+  histogram.clear();
+}
+
+function promiseStoppedLoad(expectedURL) {
+  return new Promise(resolve => {
+    let browser = gBrowser.selectedBrowser;
+    let original = browser.loadURIWithFlags;
+    browser.loadURIWithFlags = function(URI) {
+      if (URI == expectedURL) {
+        browser.loadURIWithFlags = original;
+        ok(true, "loaded expected url: " + URI);
+        resolve();
+        return;
+      }
+
+      original.apply(browser, arguments);
+    };
+  });
+}
+
+var gTests = [
+
+{
+  desc: "Test the 'Keep Current Settings' button.",
+  run: function* () {
+    let engine = yield promiseNewEngine(kTestEngine, {setAsCurrent: true});
+
+    let expectedURL = engine.
+                      getSubmission(kSearchStr, null, kSearchPurpose).
+                      uri.spec;
+
+    let rawEngine = engine.wrappedJSObject;
+    let initialHash = rawEngine.getAttr("loadPathHash");
+    rawEngine.setAttr("loadPathHash", "broken");
+
+    let loadPromise = promiseStoppedLoad(expectedURL);
+    gBrowser.contentDocument.getElementById("searchResetKeepCurrent").click();
+    yield loadPromise;
+
+    is(engine, Services.search.currentEngine,
+       "the custom engine is still default");
+    is(rawEngine.getAttr("loadPathHash"), initialHash,
+       "the loadPathHash has been fixed");
+
+    checkTelemetryRecords(TELEMETRY_RESULT_ENUM.KEPT_CURRENT);
+  }
+},
+
+{
+  desc: "Test the 'Restore Search Defaults' button.",
+  run: function* () {
+    let currentEngine = Services.search.currentEngine;
+    let originalEngine = Services.search.originalDefaultEngine;
+    let expectedURL = originalEngine.
+                      getSubmission(kSearchStr, null, kSearchPurpose).
+                      uri.spec;
+
+    let loadPromise = promiseStoppedLoad(expectedURL);
+    let doc = gBrowser.contentDocument;
+    let button = doc.getElementById("searchResetChangeEngine");
+    is(doc.activeElement, button,
+       "the 'Change Search Engine' button is focused");
+    button.click();
+    yield loadPromise;
+
+    is(originalEngine, Services.search.currentEngine,
+       "the default engine is back to the original one");
+
+    checkTelemetryRecords(TELEMETRY_RESULT_ENUM.RESTORED_DEFAULT);
+    Services.search.currentEngine = currentEngine;
+  }
+},
+
+{
+  desc: "Test the engine selector drop down.",
+  run: function* () {
+    let originalEngineName = Services.search.originalDefaultEngine.name;
+
+    let doc = gBrowser.contentDocument;
+    let list = doc.getElementById("defaultEngine");
+    is(list.value, originalEngineName,
+       "the default selection of the dropdown is the original default engine");
+
+    let defaultEngines = Services.search.getDefaultEngines();
+    is(list.childNodes.length, defaultEngines.length,
+       "the dropdown has the correct count of engines");
+
+    // Select an engine that isn't the original default one.
+    let engine;
+    for (let i = 0; i < defaultEngines.length; ++i) {
+      if (defaultEngines[i].name != originalEngineName) {
+        engine = defaultEngines[i];
+        engine.hidden = true;
+        break;
+      }
+    }
+    list.value = engine.name;
+
+    let expectedURL = engine.getSubmission(kSearchStr, null, kSearchPurpose)
+                            .uri.spec;
+    let loadPromise = promiseStoppedLoad(expectedURL);
+    doc.getElementById("searchResetChangeEngine").click();
+    yield loadPromise;
+
+    ok(!engine.hidden, "the selected engine has been unhidden");
+    is(engine, Services.search.currentEngine,
+       "the current engine is what was selected in the drop down");
+
+    checkTelemetryRecords(TELEMETRY_RESULT_ENUM.CHANGED_ENGINE);
+  }
+},
+
+{
+  desc: "Load another page without clicking any of the buttons.",
+  run: function* () {
+    yield promiseTabLoadEvent(gBrowser.selectedTab, "about:mozilla");
+
+    checkTelemetryRecords(TELEMETRY_RESULT_ENUM.CLOSED_PAGE);
+  }
+},
+
+];
+
+function test()
+{
+  waitForExplicitFinish();
+  Task.spawn(function* () {
+    let oldCanRecord = Services.telemetry.canRecordExtended;
+    Services.telemetry.canRecordExtended = true;
+    checkTelemetryRecords();
+
+    for (let test of gTests) {
+      info(test.desc);
+
+      // Create a tab to run the test.
+      let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+      // Start loading about:searchreset and wait for it to complete.
+      let url = "about:searchreset?data=" + encodeURIComponent(kSearchStr) +
+                "&purpose=" + kSearchPurpose;
+      yield promiseTabLoadEvent(tab, url);
+
+      info("Running test");
+      yield test.run();
+
+      info("Cleanup");
+      gBrowser.removeCurrentTab();
+    }
+
+    Services.telemetry.canRecordExtended = oldCanRecord;
+  }).then(finish, ex => {
+    ok(false, "Unexpected Exception: " + ex);
+    finish();
+  });
+}
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+Cu.import("resource://gre/modules/Promise.jsm");
+
 /**
  * Recursively compare two objects and check that every property of expectedObj has the same value
  * on actualObj.
  */
 function isSubObjectOf(expectedObj, actualObj, name) {
   for (let prop in expectedObj) {
     if (typeof expectedObj[prop] == 'function')
       continue;
@@ -80,8 +82,57 @@ function promiseNewEngine(basename, opti
             ok(false, "addEngine failed with error code " + errCode);
             reject();
           }
         });
       }
     });
   });
 }
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ *        The tab to load into.
+ * @param [optional] url
+ *        The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url)
+{
+  let deferred = Promise.defer();
+  info("Wait tab event: load");
+
+  function handle(loadedUrl) {
+    if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+      info(`Skipping spurious load event for ${loadedUrl}`);
+      return false;
+    }
+
+    info("Tab event received: load");
+    return true;
+  }
+
+  // Create two promises: one resolved from the content process when the page
+  // loads and one that is rejected if we take too long to load the url.
+  let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+  let timeout = setTimeout(() => {
+    deferred.reject(new Error("Timed out while waiting for a 'load' event"));
+  }, 30000);
+
+  loaded.then(() => {
+    clearTimeout(timeout);
+    deferred.resolve()
+  });
+
+  if (url)
+    BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+  // Promise.all rejects if either promise rejects (i.e. if we time out) and
+  // if our loaded promise resolves before the timeout, then we resolve the
+  // timeout promise as well, causing the all promise to resolve.
+  return Promise.all([deferred.promise, loaded]);
+}
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.5.256
+Current extension version is: 1.5.276
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.256';
-var pdfjsBuild = '1c04335';
+var pdfjsVersion = '1.5.276';
+var pdfjsBuild = '41f978c';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -2807,17 +2807,16 @@ exports.AnnotationLayer = AnnotationLaye
       root.pdfjsDisplayDOMUtils);
   }
 }(this, function (exports, sharedUtil, displayDOMUtils) {
 
 var Util = sharedUtil.Util;
 var createPromiseCapability = sharedUtil.createPromiseCapability;
 var CustomStyle = displayDOMUtils.CustomStyle;
 var getDefaultSetting = displayDOMUtils.getDefaultSetting;
-var PageViewport = sharedUtil.PageViewport;
 
 /**
  * Text layer render parameters.
  *
  * @typedef {Object} TextLayerRenderParameters
  * @property {TextContent} textContent - Text content to render (the object is
  *   returned by the page's getTextContent() method).
  * @property {HTMLElement} container - HTML element that will contain text runs.
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.256';
-var pdfjsBuild = '1c04335';
+var pdfjsVersion = '1.5.276';
+var pdfjsBuild = '41f978c';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -4452,16 +4452,21 @@ var CFFDict = (function CFFDictClosure()
       // ignore empty values
       if (value.length === 0) {
         return true;
       }
       var type = this.types[key];
       // remove the array wrapping these types of values
       if (type === 'num' || type === 'sid' || type === 'offset') {
         value = value[0];
+        // Ignore invalid values (fixes bug 1068432).
+        if (isNaN(value)) {
+          warn('Invalid CFFDict value: ' + value + ', for key: ' + key + '.');
+          return true;
+        }
       }
       this.values[key] = value;
       return true;
     },
     setByName: function CFFDict_setByName(name, value) {
       if (!(name in this.nameToKeyMap)) {
         error('Invalid dictionary name "' + name + '"');
       }
@@ -37194,17 +37199,17 @@ var PartialEvaluator = (function Partial
           args.length = 0;
           operation.args = args;
           if (!(preprocessor.read(operation))) {
             break;
           }
           textState = stateManager.state;
           var fn = operation.fn;
           args = operation.args;
-          var advance;
+          var advance, diff;
 
           switch (fn | 0) {
             case OPS.setFont:
               flushTextContentItem();
               textState.fontSize = args[1];
               next(handleSetFont(args[0].name));
               return;
             case OPS.setTextRise:
@@ -37227,18 +37232,18 @@ var PartialEvaluator = (function Partial
               if (isSameTextLine && textContentItem.initialized &&
                   advance > 0 &&
                   advance <= textContentItem.fakeMultiSpaceMax) {
                 textState.translateTextLineMatrix(args[0], args[1]);
                 textContentItem.width +=
                   (args[0] - textContentItem.lastAdvanceWidth);
                 textContentItem.height +=
                   (args[1] - textContentItem.lastAdvanceHeight);
-                var diff = (args[0] - textContentItem.lastAdvanceWidth) -
-                           (args[1] - textContentItem.lastAdvanceHeight);
+                diff = (args[0] - textContentItem.lastAdvanceWidth) -
+                       (args[1] - textContentItem.lastAdvanceHeight);
                 addFakeSpaces(diff, textContentItem.str);
                 break;
               }
 
               flushTextContentItem();
               textState.translateTextLineMatrix(args[0], args[1]);
               textState.textMatrix = textState.textLineMatrix.slice();
               break;
@@ -37248,16 +37253,34 @@ var PartialEvaluator = (function Partial
               textState.translateTextLineMatrix(args[0], args[1]);
               textState.textMatrix = textState.textLineMatrix.slice();
               break;
             case OPS.nextLine:
               flushTextContentItem();
               textState.carriageReturn();
               break;
             case OPS.setTextMatrix:
+              // Optimization to treat same line movement as advance.
+              advance = textState.calcTextLineMatrixAdvance(
+                args[0], args[1], args[2], args[3], args[4], args[5]);
+              if (advance !== null && textContentItem.initialized &&
+                  advance.value > 0 &&
+                  advance.value <= textContentItem.fakeMultiSpaceMax) {
+                textState.translateTextLineMatrix(advance.width,
+                                                  advance.height);
+                textContentItem.width +=
+                  (advance.width - textContentItem.lastAdvanceWidth);
+                textContentItem.height +=
+                  (advance.height - textContentItem.lastAdvanceHeight);
+                diff = (advance.width - textContentItem.lastAdvanceWidth) -
+                       (advance.height - textContentItem.lastAdvanceHeight);
+                addFakeSpaces(diff, textContentItem.str);
+                break;
+              }
+
               flushTextContentItem();
               textState.setTextMatrix(args[0], args[1], args[2], args[3],
                 args[4], args[5]);
               textState.setTextLineMatrix(args[0], args[1], args[2], args[3],
                 args[4], args[5]);
               break;
             case OPS.setCharSpacing:
               textState.charSpacing = args[0];
@@ -38345,16 +38368,40 @@ var TextState = (function TextStateClosu
       m[4] = m[0] * x + m[2] * y + m[4];
       m[5] = m[1] * x + m[3] * y + m[5];
     },
     translateTextLineMatrix: function TextState_translateTextMatrix(x, y) {
       var m = this.textLineMatrix;
       m[4] = m[0] * x + m[2] * y + m[4];
       m[5] = m[1] * x + m[3] * y + m[5];
     },
+    calcTextLineMatrixAdvance:
+        function TextState_calcTextLineMatrixAdvance(a, b, c, d, e, f) {
+      var font = this.font;
+      if (!font) {
+        return null;
+      }
+      var m = this.textLineMatrix;
+      if (!(a === m[0] && b === m[1] && c === m[2] && d === m[3])) {
+        return null;
+      }
+      var txDiff = e - m[4], tyDiff = f - m[5];
+      if ((font.vertical && txDiff !== 0) || (!font.vertical && tyDiff !== 0)) {
+        return null;
+      }
+      var tx, ty, denominator = a * d - b * c;
+      if (font.vertical) {
+        tx = -tyDiff * c / denominator;
+        ty = tyDiff * a / denominator;
+      } else {
+        tx = txDiff * d / denominator;
+        ty = -txDiff * b / denominator;
+      }
+      return { width: tx, height: ty, value: (font.vertical ? ty : tx), };
+    },
     calcRenderMatrix: function TextState_calcRendeMatrix(ctm) {
       // 9.4.4 Text Space Details
       var tsm = [this.fontSize * this.textHScale, 0,
                 0, this.fontSize,
                 0, this.textRise];
       return Util.transform(ctm, Util.transform(this.textMatrix, tsm));
     },
     carriageReturn: function TextState_carriageReturn() {
@@ -39198,71 +39245,85 @@ var Annotation = (function AnnotationClo
     this.data.rect = this.rectangle;
     this.data.color = this.color;
     this.data.borderStyle = this.borderStyle;
     this.data.hasAppearance = !!this.appearance;
   }
 
   Annotation.prototype = {
     /**
+     * @private
+     */
+    _hasFlag: function Annotation_hasFlag(flags, flag) {
+      return !!(flags & flag);
+    },
+
+    /**
+     * @private
+     */
+    _isViewable: function Annotation_isViewable(flags) {
+      return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) &&
+             !this._hasFlag(flags, AnnotationFlag.HIDDEN) &&
+             !this._hasFlag(flags, AnnotationFlag.NOVIEW);
+    },
+
+    /**
+     * @private
+     */
+    _isPrintable: function AnnotationFlag_isPrintable(flags) {
+      return this._hasFlag(flags, AnnotationFlag.PRINT) &&
+             !this._hasFlag(flags, AnnotationFlag.INVISIBLE) &&
+             !this._hasFlag(flags, AnnotationFlag.HIDDEN);
+    },
+
+    /**
      * @return {boolean}
      */
     get viewable() {
-      if (this.flags) {
-        return !this.hasFlag(AnnotationFlag.INVISIBLE) &&
-               !this.hasFlag(AnnotationFlag.HIDDEN) &&
-               !this.hasFlag(AnnotationFlag.NOVIEW);
-      }
-      return true;
+      if (this.flags === 0) {
+        return true;
+      }
+      return this._isViewable(this.flags);
     },
 
     /**
      * @return {boolean}
      */
     get printable() {
-      if (this.flags) {
-        return this.hasFlag(AnnotationFlag.PRINT) &&
-               !this.hasFlag(AnnotationFlag.INVISIBLE) &&
-               !this.hasFlag(AnnotationFlag.HIDDEN);
-      }
-      return false;
+      if (this.flags === 0) {
+        return false;
+      }
+      return this._isPrintable(this.flags);
     },
 
     /**
      * Set the flags.
      *
      * @public
      * @memberof Annotation
      * @param {number} flags - Unsigned 32-bit integer specifying annotation
      *                         characteristics
      * @see {@link shared/util.js}
      */
     setFlags: function Annotation_setFlags(flags) {
-      if (isInt(flags)) {
-        this.flags = flags;
-      } else {
-        this.flags = 0;
-      }
+      this.flags = (isInt(flags) && flags > 0) ? flags : 0;
     },
 
     /**
      * Check if a provided flag is set.
      *
      * @public
      * @memberof Annotation
      * @param {number} flag - Hexadecimal representation for an annotation
      *                        characteristic
      * @return {boolean}
      * @see {@link shared/util.js}
      */
     hasFlag: function Annotation_hasFlag(flag) {
-      if (this.flags) {
-        return (this.flags & flag) > 0;
-      }
-      return false;
+      return this._hasFlag(this.flags, flag);
     },
 
     /**
      * Set the rectangle.
      *
      * @public
      * @memberof Annotation
      * @param {Array} rectangle - The rectangle array with exactly four entries
@@ -39830,16 +39891,26 @@ var PopupAnnotation = (function PopupAnn
 
     if (!parentItem.has('C')) {
       // Fall back to the default background color.
       this.data.color = null;
     } else {
       this.setColor(parentItem.getArray('C'));
       this.data.color = this.color;
     }
+
+    // If the Popup annotation is not viewable, but the parent annotation is,
+    // that is most likely a bug. Fallback to inherit the flags from the parent
+    // annotation (this is consistent with the behaviour in Adobe Reader).
+    if (!this.viewable) {
+      var parentFlags = parentItem.get('F');
+      if (this._isViewable(parentFlags)) {
+        this.setFlags(parentFlags);
+      }
+    }
   }
 
   Util.inherit(PopupAnnotation, Annotation, {});
 
   return PopupAnnotation;
 })();
 
 var HighlightAnnotation = (function HighlightAnnotationClosure() {
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -2951,17 +2951,17 @@ var PDFDocumentProperties = (function PD
 exports.PDFDocumentProperties = PDFDocumentProperties;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsWebPDFFindController = {}), root.pdfjsWebUIUtils);
   }
-}(this, function (exports, uiUtils, firefoxCom) {
+}(this, function (exports, uiUtils) {
 
 var scrollIntoView = uiUtils.scrollIntoView;
 
 var FindStates = {
   FIND_FOUND: 0,
   FIND_NOTFOUND: 1,
   FIND_WRAPPED: 2,
   FIND_PENDING: 3
@@ -3249,20 +3249,19 @@ var PDFFindController = (function PDFFin
 
     /**
      * The method is called back from the text layer when match presentation
      * is updated.
      * @param {number} pageIndex - page index.
      * @param {number} index - match index.
      * @param {Array} elements - text layer div elements array.
      * @param {number} beginIdx - start index of the div array for the match.
-     * @param {number} endIdx - end index of the div array for the match.
      */
     updateMatchPosition: function PDFFindController_updateMatchPosition(
-        pageIndex, index, elements, beginIdx, endIdx) {
+        pageIndex, index, elements, beginIdx) {
       if (this.selected.matchIdx === index &&
           this.selected.pageIdx === pageIndex) {
         var spot = {
           top: FIND_SCROLL_OFFSET_TOP,
           left: FIND_SCROLL_OFFSET_LEFT
         };
         scrollIntoView(elements[beginIdx], spot,
                        /* skipOverflowHiddenElements = */ true);
@@ -4990,17 +4989,16 @@ var PDFPageView = (function PDFPageViewC
       this.viewport = this.viewport.clone({
         scale: this.scale * CSS_UNITS,
         rotation: totalRotation
       });
 
       var isScalingRestricted = false;
       if (this.canvas && pdfjsLib.PDFJS.maxCanvasPixels > 0) {
         var outputScale = this.outputScale;
-        var pixelsInViewport = this.viewport.width * this.viewport.height;
         if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
             ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
             pdfjsLib.PDFJS.maxCanvasPixels) {
           isScalingRestricted = true;
         }
       }
 
       if (this.canvas) {
@@ -5796,17 +5794,17 @@ var TextLayerBuilder = (function TextLay
         var match = matches[i];
         var begin = match.begin;
         var end = match.end;
         var isSelected = (isSelectedPage && i === selectedMatchIdx);
         var highlightSuffix = (isSelected ? ' selected' : '');
 
         if (this.findController) {
           this.findController.updateMatchPosition(pageIdx, i, textDivs,
-                                                  begin.divIdx, end.divIdx);
+                                                  begin.divIdx);
         }
 
         // Match inside new div.
         if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
           // If there was a previous div, then add the text at the end.
           if (prevEnd !== null) {
             appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
           }
@@ -7565,17 +7563,16 @@ var PDFViewerApplication = {
     var baseDocumentUrl = this.url.split('#')[0];
     this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
 
     var pdfViewer = this.pdfViewer;
     pdfViewer.currentScale = scale;
     pdfViewer.setDocument(pdfDocument);
     var firstPagePromise = pdfViewer.firstPagePromise;
     var pagesPromise = pdfViewer.pagesPromise;
-    var onePageRendered = pdfViewer.onePageRendered;
 
     this.pageRotation = 0;
 
     this.pdfThumbnailViewer.setDocument(pdfDocument);
 
     firstPagePromise.then(function(pdfPage) {
       downloadedPromise.then(function () {
         self.eventBus.dispatch('documentload', {source: self});
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutSearchReset.dtd
@@ -0,0 +1,28 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY searchreset.tabtitle       "Restore Search Settings">
+
+<!ENTITY searchreset.pageTitle      "Restore your search settings?">
+
+<!ENTITY searchreset.pageInfo1      "Your search settings might be out-of-date. Firefox can help you restore the default search settings.">
+
+<!ENTITY searchreset.selector.label "This will set your default search engine to">
+
+<!-- LOCALIZATION NOTE (searchreset.beforelink.pageInfo,
+searchreset.afterlink.pageInfo): these two string are used respectively
+before and after the the "Settings page" link (searchreset.link.pageInfo).
+Localizers can use one of them, or both, to better adapt this sentence to
+their language.
+-->
+<!ENTITY searchreset.beforelink.pageInfo2 "You can change these settings at any time from the ">
+<!ENTITY searchreset.afterlink.pageInfo2  ".">
+
+<!ENTITY searchreset.link.pageInfo2       "Settings page">
+
+<!ENTITY searchreset.noChangeButton        "Don’t Change">
+<!ENTITY searchreset.noChangeButton.access "D">
+
+<!ENTITY searchreset.changeEngineButton        "Change Search Engine">
+<!ENTITY searchreset.changeEngineButton.access "C">
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -677,17 +677,17 @@ you can use these alternative items. Oth
 <!ENTITY spellAddDictionaries.accesskey "A">
 
 <!ENTITY editBookmark.done.label                     "Done">
 <!ENTITY editBookmark.removeBookmark.accessKey       "R">
 
 <!ENTITY identity.connectionSecure "Secure Connection">
 <!ENTITY identity.connectionNotSecure "Connection is Not Secure">
 <!ENTITY identity.connectionFile "This page is stored on your computer.">
-<!ENTITY identity.connectionVerified1 "You are securely connected to this site, run by:">
+<!ENTITY identity.connectionVerified2 "You are securely connected to this site, owned by:">
 <!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
 <!ENTITY identity.insecureLoginForms2 "Logins entered on this page could be compromised.">
 
 <!-- Strings for connection state warnings. -->
 <!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
 <!ENTITY identity.passiveLoaded "Parts of this page are not secure (such as images).">
 <!ENTITY identity.activeLoaded "You have disabled protection on this page.">
 <!ENTITY identity.weakEncryption "This page uses weak encryption.">
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -12,16 +12,17 @@
     locale/browser/aboutPrivateBrowsing.dtd        (%chrome/browser/aboutPrivateBrowsing.dtd)
     locale/browser/aboutPrivateBrowsing.properties (%chrome/browser/aboutPrivateBrowsing.properties)
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/aboutHome.dtd                   (%chrome/browser/aboutHome.dtd)
     locale/browser/accounts.properties             (%chrome/browser/accounts.properties)
 #ifdef MOZ_SERVICES_HEALTHREPORT
     locale/browser/aboutHealthReport.dtd           (%chrome/browser/aboutHealthReport.dtd)
 #endif
+    locale/browser/aboutSearchReset.dtd            (%chrome/browser/aboutSearchReset.dtd)
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
     locale/browser/aboutTabCrashed.dtd             (%chrome/browser/aboutTabCrashed.dtd)
     locale/browser/syncCustomize.dtd               (%chrome/browser/syncCustomize.dtd)
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1013,18 +1013,17 @@ menuitem:not([type]):not(.menuitem-toolt
 #notification-popup-box {
   border-radius: 2.5px 0 0 2.5px;
 }
 
 .notification-anchor-icon:-moz-focusring {
   outline: 1px dotted -moz-DialogText;
 }
 
-.indexedDB-notification-icon,
-#indexedDB-notification-icon {
+.indexedDB-icon {
   list-style-image: url(moz-icon://stock/gtk-dialog-question?size=16);
 }
 
 /* Translation infobar */
 
 %include ../shared/translation/infobar.inc.css
 
 notification[value="translation"] {
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/favicon-search-16.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <circle cx="8" cy="8" r="8" fill="#58bf43"/>
+  <circle cx="8" cy="8" r="7.5" stroke="#41a833" stroke-width="1" fill="none"/>
+  <path d="M12.879,12L12,12.879,9.015,9.9A4.276,4.276,0,1,1,9.9,9.015ZM6.5,3.536A2.964,2.964,0,1,0,9.464,6.5,2.964,2.964,0,0,0,6.5,3.536Z" stroke="#41a833" stroke-width="2" fill="none"/>
+  <path d="M12.879,12L12,12.879,9.015,9.9A4.276,4.276,0,1,1,9.9,9.015ZM6.5,3.536A2.964,2.964,0,1,0,9.464,6.5,2.964,2.964,0,0,0,6.5,3.536Z" fill="#fff"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/icon-search-64.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
+  <ellipse cx="32" cy="34" rx="29.5" ry="30" fill="#000" fill-opacity=".1"/>
+  <circle cx="32" cy="32" r="30" fill="#58bf43"/>
+  <circle cx="32" cy="32" r="29.5" stroke="#41a833" stroke-width="1" fill="none"/>
+  <path d="M50,47.131L47.131,50,36.776,39.647a16.038,16.038,0,1,1,2.871-2.871ZM27,15A12,12,0,1,0,39,27,12,12,0,0,0,27,15Z" stroke="#41a833" stroke-width="2" fill="none"/>
+  <path d="M50,47.131L47.131,50,36.776,39.647a16.038,16.038,0,1,1,2.871-2.871ZM27,15A12,12,0,1,0,39,27,12,12,0,0,0,27,15Z" fill="#fff"/>
+  <circle cx="27" cy="27" r="13" fill="#fff" fill-opacity=".2"/>
+</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -83,16 +83,17 @@
   skin/classic/browser/fxa/android@2x.png                      (../shared/fxa/android@2x.png)
   skin/classic/browser/fxa/ios.png                             (../shared/fxa/ios.png)
   skin/classic/browser/fxa/ios@2x.png                          (../shared/fxa/ios@2x.png)
   skin/classic/browser/search-pref.png                         (../shared/search/search-pref.png)
   skin/classic/browser/search-indicator.png                    (../shared/search/search-indicator.png)
   skin/classic/browser/search-indicator@2x.png                 (../shared/search/search-indicator@2x.png)
   skin/classic/browser/search-engine-placeholder.png           (../shared/search/search-engine-placeholder.png)
   skin/classic/browser/search-engine-placeholder@2x.png        (../shared/search/search-engine-placeholder@2x.png)
+  skin/classic/browser/searchReset.css                         (../shared/searchReset.css)
   skin/classic/browser/badge-add-engine.png                    (../shared/search/badge-add-engine.png)
   skin/classic/browser/badge-add-engine@2x.png                 (../shared/search/badge-add-engine@2x.png)
   skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/social/chat-icons.svg                   (../shared/social/chat-icons.svg)
@@ -114,16 +115,18 @@
   skin/classic/browser/update-badge.svg                        (../shared/update-badge.svg)
   skin/classic/browser/update-badge-failed.svg                 (../shared/update-badge-failed.svg)
   skin/classic/browser/urlbar-arrow.png                        (../shared/urlbar-arrow.png)
   skin/classic/browser/urlbar-arrow@2x.png                     (../shared/urlbar-arrow@2x.png)
   skin/classic/browser/warning.svg                             (../shared/warning.svg)
   skin/classic/browser/cert-error.svg                          (../shared/incontent-icons/cert-error.svg)
   skin/classic/browser/session-restore.svg                     (../shared/incontent-icons/session-restore.svg)
   skin/classic/browser/tab-crashed.svg                         (../shared/incontent-icons/tab-crashed.svg)
+  skin/classic/browser/favicon-search-16.svg                   (../shared/favicon-search-16.svg)
+  skin/classic/browser/icon-search-64.svg                      (../shared/incontent-icons/icon-search-64.svg)
   skin/classic/browser/welcome-back.svg                        (../shared/incontent-icons/welcome-back.svg)
   skin/classic/browser/reader-tour.png                         (../shared/reader/reader-tour.png)
   skin/classic/browser/reader-tour@2x.png                      (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                          (../shared/reader/readerMode.svg)
   skin/classic/browser/notification-pluginNormal.png           (../shared/plugins/notification-pluginNormal.png)
   skin/classic/browser/notification-pluginNormal@2x.png        (../shared/plugins/notification-pluginNormal@2x.png)
   skin/classic/browser/notification-pluginAlert.png            (../shared/plugins/notification-pluginAlert.png)
   skin/classic/browser/notification-pluginAlert@2x.png         (../shared/plugins/notification-pluginAlert@2x.png)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -76,16 +76,20 @@
 .popup-notification-icon[popupid="webRTC-shareScreen"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareScreen-64.png);
 }
 
 .popup-notification-icon[popupid="pointerLock"] {
   list-style-image: url(chrome://browser/skin/pointerLock-64.png);
 }
 
+.popup-notification-icon[popupid="servicesInstall"] {
+  list-style-image: url(chrome://browser/skin/social/services-64.png);
+}
+
 /* Notification icon box */
 #notification-popup-box {
   position: relative;
   background-color: #fff;
   background-clip: padding-box;
   padding-left: 3px;
   border-width: 0 8px 0 0;
   border-style: solid;
@@ -106,80 +110,90 @@
 }
 
 /* For the anchor icons in the chat window, we don't have the notification popup box,
    so we need to cancel the RTL transform. */
 .notification-anchor-icon.chat-toolbarbutton:-moz-locale-dir(rtl) {
   transform: none;
 }
 
+/* This class can be used alone or in combination with the class defining the
+   type of icon displayed. This rule must be defined before the others in order
+   for its list-style-image to be overridden. */
 .notification-anchor-icon {
 %ifdef MOZ_WIDGET_GTK
   list-style-image: url(moz-icon://stock/gtk-dialog-info?size=16);
 %else
   list-style-image: url(chrome://global/skin/icons/information-16.png);
 %endif
   width: 16px;
   height: 16px;
   margin: 0 2px;
 }
 
-.geo-notification-icon,
-#geo-notification-icon {
+@media (min-resolution: 1.1dppx) {
+  .notification-anchor-icon {
+%ifdef MOZ_WIDGET_GTK
+    list-style-image: url(moz-icon://stock/gtk-dialog-info?size=dialog);
+%else
+    list-style-image: url(chrome://global/skin/icons/information-32.png);
+%endif
+  }
+}
+
+.geo-icon {
   list-style-image: url(chrome://browser/skin/Geolocation-16.png);
 }
 
-#addons-notification-icon {
+.install-icon {
   list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg#default);
 }
 
-#addons-notification-icon:hover {
+.install-icon:hover {
   list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg#hover);
 }
 
-#addons-notification-icon:hover:active {
+.install-icon:hover:active {
   list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg#active);
 }
 
-.indexedDB-notification-icon,
-#indexedDB-notification-icon {
+.indexedDB-icon {
   list-style-image: url(chrome://global/skin/icons/question-16.png);
 }
 
-#password-notification-icon {
+.login-icon {
   list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
 }
 
 #login-fill-notification-icon {
-  /* Temporary icon until the capture and fill doorhangers are unified. */
-  list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
+  /* Temporary solution until the capture and fill doorhangers are unified. */
   transform: scaleX(-1);
 }
 
-#plugins-notification-icon {
+.plugin-icon {
   list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
 }
 
-#plugins-notification-icon.plugin-hidden {
+.plugin-icon.plugin-hidden {
   list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
 }
 
-#plugins-notification-icon.plugin-blocked {
+.plugin-icon.plugin-blocked {
   list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
 }
 
-#plugins-notification-icon {
+.plugin-icon {
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-#plugins-notification-icon:hover {
+.plugin-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-#plugins-notification-icon:active {
+.plugin-icon:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
@@ -193,94 +207,82 @@
   from {
     opacity: 0;
   }
   to {
     opacity: 1;
   }
 }
 
-.webRTC-shareDevices-notification-icon,
-#webRTC-shareDevices-notification-icon {
+.camera-icon {
   list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png);
 }
 
+/* The first selector is used by socialchat.xml (bug 1275558). */
 .webRTC-sharingDevices-notification-icon,
-#webRTC-sharingDevices-notification-icon {
+.camera-icon.in-use {
   list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png);
 }
 
-.webRTC-shareMicrophone-notification-icon,
-#webRTC-shareMicrophone-notification-icon {
+.microphone-icon {
   list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16.png);
 }
 
+/* The first selector is used by socialchat.xml (bug 1275558). */
 .webRTC-sharingMicrophone-notification-icon,
-#webRTC-sharingMicrophone-notification-icon {
+.microphone-icon.in-use {
   list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16.png);
 }
 
-.webRTC-shareScreen-notification-icon,
-#webRTC-shareScreen-notification-icon {
+.screen-icon {
   list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16.png);
 }
 
-.webRTC-sharingScreen-notification-icon,
-#webRTC-sharingScreen-notification-icon {
+.screen-icon.in-use {
   list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-16.png);
 }
 
-.web-notifications-notification-icon,
-#web-notifications-notification-icon {
+.web-notifications-icon {
   list-style-image: url(chrome://browser/skin/web-notifications-tray.svg);
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-.web-notifications-notification-icon:hover,
-#web-notifications-notification-icon:hover {
+.web-notifications-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-.web-notifications-notification-icon:hover:active,
-#web-notifications-notification-icon:hover:active {
+.web-notifications-icon:hover:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
-.pointerLock-notification-icon,
-#pointerLock-notification-icon {
+.pointer-icon {
   list-style-image: url(chrome://browser/skin/pointerLock-16.png);
 }
 
-.translate-notification-icon,
-#translate-notification-icon {
+.service-icon {
+  list-style-image: url(chrome://browser/skin/social/services-16.png);
+}
+
+.translation-icon {
   list-style-image: url(chrome://browser/skin/translation-16.png);
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 
-.translated-notification-icon,
-#translated-notification-icon {
-  list-style-image: url(chrome://browser/skin/translation-16.png);
+.translation-icon.in-use {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 
-.popup-notification-icon[popupid="servicesInstall"] {
-  list-style-image: url(chrome://browser/skin/social/services-64.png);
-}
-#servicesInstall-notification-icon {
-  list-style-image: url(chrome://browser/skin/social/services-16.png);
-}
-
 /* EME notifications */
 
 .popup-notification-icon[popupid="drmContentPlaying"],
-#eme-notification-icon {
+.drm-icon {
   list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
 }
 
-#eme-notification-icon:hover:active {
+.drm-icon:hover:active {
   list-style-image: url("chrome://browser/skin/drm-icon.svg#chains-pressed");
 }
 
 #eme-notification-icon[firstplay=true] {
   animation: emeTeachingMoment 0.2s linear 0s 5 normal;
 }
 
 @keyframes emeTeachingMoment {
@@ -291,51 +293,37 @@
 }
 
 /* HiDPI notification icons */
 @media (min-resolution: 1.1dppx) {
   #notification-popup-box {
     border-image: url("chrome://browser/skin/urlbar-arrow@2x.png") 0 16 0 0 fill;
   }
 
-  .notification-anchor-icon {
-%ifdef MOZ_WIDGET_GTK
-    list-style-image: url(moz-icon://stock/gtk-dialog-info?size=dialog);
-%else
-    list-style-image: url(chrome://global/skin/icons/information-32.png);
-%endif
-  }
-
-  .webRTC-shareDevices-notification-icon,
-  #webRTC-shareDevices-notification-icon {
+  .camera-icon {
     list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16@2x.png);
   }
 
-  .webRTC-sharingDevices-notification-icon,
-  #webRTC-sharingDevices-notification-icon {
+  .camera-icon.in-use {
     list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16@2x.png);
   }
 
-  .webRTC-shareMicrophone-notification-icon,
-  #webRTC-shareMicrophone-notification-icon {
+  .microphone-icon {
     list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16@2x.png);
   }
 
-  .webRTC-sharingMicrophone-notification-icon,
-  #webRTC-sharingMicrophone-notification-icon {
+  .microphone-icon.in-use {
     list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16@2x.png);
   }
 
-  .webRTC-shareScreen-notification-icon,
-  #webRTC-shareScreen-notification-icon {
+  .screen-icon {
     list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16@2x.png);
   }
 
-  .webRTC-sharingScreen-notification-icon,
-  #webRTC-sharingScreen-notification-icon {
+  .screen-icon.in-use {
     list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-16@2x.png);
   }
 
   .popup-notification-icon[popupid="webRTC-sharingDevices"],
   .popup-notification-icon[popupid="webRTC-shareDevices"] {
     list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64@2x.png);
   }
 
@@ -346,81 +334,74 @@
 
   .popup-notification-icon[popupid="webRTC-sharingScreen"],
   .popup-notification-icon[popupid="webRTC-shareScreen"] {
     list-style-image: url(chrome://browser/skin/webRTC-shareScreen-64@2x.png);
   }
 
 %ifdef XP_MACOSX
 /* OSX only until we have icons for Windows and Linux */
-  .geo-notification-icon,
-  #geo-notification-icon {
+  .geo-icon {
     list-style-image: url(chrome://browser/skin/Geolocation-16@2x.png);
   }
 
-  .indexedDB-notification-icon,
-  #indexedDB-notification-icon {
+  .indexedDB-icon {
     list-style-image: url(chrome://global/skin/icons/question-32.png);
   }
 
-  #login-fill-notification-icon,
-  #password-notification-icon {
+  .login-icon {
     list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16@2x.png);
   }
 
-  #plugins-notification-icon {
+  .plugin-icon {
     list-style-image: url(chrome://browser/skin/notification-pluginNormal@2x.png);
   }
 
-  #plugins-notification-icon.plugin-hidden {
+  .plugin-icon.plugin-hidden {
     list-style-image: url(chrome://browser/skin/notification-pluginAlert@2x.png);
   }
 
-  #plugins-notification-icon.plugin-blocked {
+  .plugin-icon.plugin-blocked {
     list-style-image: url(chrome://browser/skin/notification-pluginBlocked@2x.png);
   }
 
-  #plugins-notification-icon {
+  .plugin-icon {
     -moz-image-region: rect(0, 32px, 32px, 0);
   }
 
-  #plugins-notification-icon:hover {
+  .plugin-icon:hover {
     -moz-image-region: rect(0, 64px, 32px, 32px);
   }
 
-  #plugins-notification-icon:active {
+  .plugin-icon:active {
     -moz-image-region: rect(0, 96px, 32px, 64px);
   }
 
-  .pointerLock-notification-icon,
-  #pointerLock-notification-icon {
+  .pointer-icon {
     list-style-image: url(chrome://browser/skin/pointerLock-16@2x.png);
   }
 
-  .translate-notification-icon,
-  #translate-notification-icon {
+  .translation-icon {
     list-style-image: url(chrome://browser/skin/translation-16@2x.png);
     -moz-image-region: rect(0px, 32px, 32px, 0px);
   }
 
-  .translated-notification-icon,
-  #translated-notification-icon {
-    list-style-image: url(chrome://browser/skin/translation-16@2x.png);
+  .translation-icon.in-use {
     -moz-image-region: rect(0px, 64px, 32px, 32px);
   }
 
   .popup-notification-icon[popupid="geolocation"] {
     list-style-image: url(chrome://browser/skin/Geolocation-64@2x.png);
   }
 
   .popup-notification-icon[popupid="pointerLock"] {
     list-style-image: url(chrome://browser/skin/pointerLock-64@2x.png);
   }
 
   .popup-notification-icon[popupid="servicesInstall"] {
     list-style-image: url(chrome://browser/skin/social/services-64@2x.png);
   }
 
-  #servicesInstall-notification-icon {
+  .service-icon {
     list-style-image: url(chrome://browser/skin/social/services-16@2x.png);
   }
 %endif
 }
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/searchReset.css
@@ -0,0 +1,36 @@
+/* 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/. */
+
+body {
+  align-items: center;
+}
+
+.title {
+  background-image: url("chrome://browser/skin/icon-search-64.svg");
+}
+
+select {
+  font: inherit;
+  padding-inline-end: 24px;
+  padding-inline-start: 26px;
+  background-image: var(--engine-icon-url),
+                    url("chrome://global/skin/in-content/dropdown.svg#dropdown");
+  background-repeat: no-repeat;
+  background-position: 8px center, calc(100% - 4px) center;
+  background-size: 16px, 16px;
+}
+
+select:-moz-focusring {
+  color: transparent;
+  text-shadow: 0 0 0 var(--in-content-text-color);
+}
+
+option {
+  padding: 4px;
+  padding-inline-start: 30px;
+  background-repeat: no-repeat;
+  background-position: 8px center;
+  background-size: 16px;
+  background-color: var(--in-content-page-background);
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -86,18 +86,18 @@
    are not autohiding the menubar. */
 #toolbar-menubar:not([autohide="true"]) + #TabsToolbar > .titlebar-placeholder[type="caption-buttons"] {
   display: none;
 }
 
 /* We want a 4px gap between the TabsToolbar and the toolbar-menubar when the
    toolbar-menu is displayed, and a 16px gap when it is not. 1px is taken care
    of by the (light) outer shadow of the tab, the remaining 3/15 are these margins. */
-#toolbar-menubar:not([moz-collapsed=true]):not([autohide=true]) ~ #TabsToolbar,
-#toolbar-menubar:not([moz-collapsed=true])[autohide=true]:not([inactive]) ~ #TabsToolbar {
+#toolbar-menubar:not([autohide=true]) ~ #TabsToolbar:not([inFullscreen]),
+#toolbar-menubar[autohide=true]:not([inactive]) ~ #TabsToolbar:not([inFullscreen]) {
   margin-top: 3px;
 }
 
 #main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
 #main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
   margin-top: var(--space-above-tabbar);
 }
 
--- a/browser/tools/mozscreenshots/browser.ini
+++ b/browser/tools/mozscreenshots/browser.ini
@@ -1,6 +1,8 @@
 [DEFAULT]
 subsuite = screenshots
 support-files =
   head.js
+  mozscreenshots/extension/lib/permissionPrompts.html
+  mozscreenshots/extension/lib/borderify.xpi
 
 [browser_screenshots.js]
--- a/browser/tools/mozscreenshots/devtools/browser_devtools.js
+++ b/browser/tools/mozscreenshots/devtools/browser_devtools.js
@@ -5,10 +5,10 @@
 "use strict";
 
 add_task(function* capture() {
   if (!shouldCapture()) {
     return;
   }
   let sets = ["DevTools"];
 
-  yield TestRunner.start(sets);
+  yield TestRunner.start(sets, "devtools");
 });
--- a/browser/tools/mozscreenshots/moz.build
+++ b/browser/tools/mozscreenshots/moz.build
@@ -4,15 +4,16 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += [
     # Each test is in it's own directory so it gets run in a clean profile with
     # run-by-dir.
     'browser.ini',
     'devtools/browser.ini',
+    'permissionPrompts/browser.ini',
     'preferences/browser.ini',
     'primaryUI/browser.ini',
 ]
 
 TEST_DIRS += [
     'mozscreenshots/extension',
 ]
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
@@ -2,17 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["TestRunner"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-const defaultSetNames = ["TabsInTitlebar", "Tabs", "WindowSize", "Toolbars", "LightweightThemes"];
 const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
 const HOME_PAGE = "chrome://mozscreenshots/content/lib/mozscreenshots.html";
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -46,31 +45,33 @@ this.TestRunner = {
   init(extensionPath) {
     log.debug("init");
     this._extensionPath = extensionPath;
   },
 
   /**
    * Load specified sets, execute all combinations of them, and capture screenshots.
    */
-  start: Task.async(function*(setNames = null) {
-    setNames = setNames || defaultSetNames;
-
+  start: Task.async(function*(setNames, jobName = null) {
     let subDirs = ["mozscreenshots",
                    (new Date()).toISOString().replace(/:/g, "-") + "_" + Services.appinfo.OS];
     let screenshotPath = FileUtils.getFile("TmpD", subDirs).path;
 
     const MOZ_UPLOAD_DIR = env.get("MOZ_UPLOAD_DIR");
     if (MOZ_UPLOAD_DIR) {
       screenshotPath = MOZ_UPLOAD_DIR;
     }
 
     log.info("Saving screenshots to:", screenshotPath);
 
-    let screenshotPrefix = Services.appinfo.appBuildID + "_";
+    let screenshotPrefix = Services.appinfo.appBuildID;
+    if (jobName) {
+      screenshotPrefix += "-" + jobName;
+    }
+    screenshotPrefix += "_";
     Screenshot.init(screenshotPath, this._extensionPath, screenshotPrefix);
     this._libDir = this._extensionPath.QueryInterface(Ci.nsIFileURL).file.clone();
     this._libDir.append("chrome");
     this._libDir.append("mozscreenshots");
     this._libDir.append("lib");
 
     let sets = this.loadSets(setNames);
 
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
@@ -26,25 +26,26 @@ this.DevTools = {
   init(libDir) {
     let panels = ["options", "webconsole", "jsdebugger", "styleeditor",
                   "performance", "netmonitor"];
 
     panels.forEach(panel => {
       this.configurations[panel] = {};
       this.configurations[panel].applyConfig = Task.async(function* () {
         yield gDevTools.showToolbox(getTargetForSelectedTab(), panel, "bottom");
+        yield new Promise(resolve => setTimeout(resolve, 500));
       });
     });
   },
 
   configurations: {
     bottomToolbox: {
       applyConfig: Task.async(function* () {
         yield gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "bottom");
-        yield new Promise(resolve => setTimeout(resolve, 500));
+        yield new Promise(resolve => setTimeout(resolve, 1000));
       }),
     },
     sideToolbox: {
       applyConfig: Task.async(function* () {
         yield gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "side");
         yield new Promise(resolve => setTimeout(resolve, 500));
       }),
     },
new file mode 100644
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PermissionPrompts"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/E10SUtils.jsm");
+Cu.import("resource://testing-common/ContentTask.jsm");
+Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+
+const URL = "https://test1.example.com/extensions/mozscreenshots/browser/chrome/mozscreenshots/lib/permissionPrompts.html";
+let lastTab = null;
+
+this.PermissionPrompts = {
+  init(libDir) {
+    Services.prefs.setBoolPref("media.navigator.permission.fake", true);
+    Services.prefs.setCharPref("media.getusermedia.screensharing.allowed_domains",
+                               "test1.example.com");
+    Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
+  },
+
+  configurations: {
+    shareDevices: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#webRTC-shareDevices");
+      }),
+    },
+
+    shareMicrophone: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#webRTC-shareMicrophone");
+      }),
+    },
+
+    shareVideoAndMicrophone: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#webRTC-shareDevices2");
+      }),
+    },
+
+    shareScreen: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#webRTC-shareScreen");
+      }),
+    },
+
+    geo: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#geo");
+      }),
+    },
+
+    loginCapture: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#login-capture", URL);
+      }),
+    },
+
+    notifications: {
+      applyConfig: Task.async(function*() {
+        yield closeLastTab();
+        yield clickOn("#web-notifications", URL);
+      }),
+    },
+
+    addons: {
+      applyConfig: Task.async(function*() {
+        Services.prefs.setBoolPref("xpinstall.whitelist.required", true);
+
+        yield closeLastTab();
+        yield clickOn("#addons", URL);
+      }),
+    },
+
+    addonsNoWhitelist: {
+      applyConfig: Task.async(function*() {
+        Services.prefs.setBoolPref("xpinstall.whitelist.required", false);
+
+        let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+        let notification = browserWindow.document.getElementById("addon-install-confirmation-notification");
+
+        yield closeLastTab();
+        yield clickOn("#addons", URL);
+
+        // We want to skip the progress-notification, so we wait for
+        // the install-confirmation screen to be "not hidden" = shown.
+        yield BrowserTestUtils.waitForCondition(() => !notification.hasAttribute("hidden"),
+                                                "addon install confirmation did not show", 200);
+      }),
+    },
+  },
+};
+
+function* closeLastTab(selector) {
+  if (!lastTab) {
+    return;
+  }
+  let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+  yield BrowserTestUtils.removeTab(lastTab);
+  lastTab = null;
+}
+
+function* clickOn(selector) {
+  let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+
+  // Save the tab so we can close it later.
+  lastTab = yield BrowserTestUtils.openNewForegroundTab(browserWindow.gBrowser, URL);
+
+  yield ContentTask.spawn(lastTab.linkedBrowser, selector, function* (arg) {
+    E10SUtils.wrapHandlingUserInput(content, true, function() {
+      let element = content.document.querySelector(arg);
+      element.click();
+    });
+  });
+
+  // Wait for the popup to actually be shown before making the screenshot
+  yield BrowserTestUtils.waitForEvent(browserWindow.PopupNotifications.panel, "popupshown");
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..66ae92ed21152aa799d6d0a273bae874ecb4438f
GIT binary patch
literal 1611
zc$^FHW@h1H0D;TC%RCs602>fzCg<lB>jyy9aDdf(y3;Vjjgf&Nj){SR7bu#Wn3tKB
zT3n)+Rh*v}JJHwguz^79cgxn)ve0B^t)Q!-3SU`7lNwi@*top&VEv}MK`W}tbC$PE
zd0H{=dF|(CA)y?JjE&VV7j0o%FHk3>q|nwB8l=F_wDlVI2aTvBQ=H~su)5~xFw6Cx
z_hrT0{Y!&-BJK-rVRWeN(OaV-TELJhTVXUYz~JlYHnr)uW;w(!jFnfOp-{Zl&giX3
z%j|=}@AiGLQO-(x!snF3_u%k>%6O@^%o7*RU;kw<xleif;plwRluv2zL-YOwl^K2e
zJ*{B%O0l+Oe-l_%J!EoO%)Ve&E62Ql&6903FCPik3fdX*?B&ysi`IyHKk{i0{%@l{
z(fIk4CpzwSKW{Sr-;g|0z1t>hOZA*15o@|NT=Xk*wwO#V`S3{bZLG?3``bsRn022m
zc>m^4n9AbBotjhF<}J-h3!gsw;oBF#>^j`cCdqAEe1F@jb65FM13anpb#ow)mjJ{(
zK%A6cl#*JMnN|r5@ypjAebUm^3-r+R(h2tn@i?XL<*6H_aZ3NJcc8{ek5G@3{(5J9
z&uU-vKIOmi%HvCyo;!(Z>a9HEd9^ufb7+8(?ur17bLUU`1ZsHs25x%B#DHpbwvUPL
zVo=&(fW$Q<J@|M!yZQyY>h?JDH5mxFexH}br?9qDkH>mZr*?Q%W6&|9g(V)=Gj=>y
zsIv4+axMF>{(f(Dx5G5%GnaQiyOE&7=Wfbk?k~T9CCQ5CL&u%wlrM+g*c`gb;btUv
zZpMk$r4s_SB{+TFe5{4HNAbFt_TgHlI-Tbt7ddP{KiGF(Dnx1GtH2KD!ff9or-Iub
zoJ{JQcA<-B>9w-2Z{mYx=Eluj@;ZEC&%M=i!)BwUT-F&nUrzvej=<E*38aHu9bJ4~
z^>R~U`wwOvG2n4||F27N=Tz0b@5@E1!g5ptJ=PrEwA*+8zFRsXyVN;NW>sEJzA|aY
ziISEJ-+#ocaMGHzsrAFgj>i4k>B46tX8$xizE*WxHb=gK%h|eh8zP>B>A(2*r|s%4
z?pK)^|F#vanNzcp>lAyQNwwSQb#B{V_vc9SMm_t^7v<OL*O#=xOzp1cC(k4L6Fpkr
zJySWK5D@tNOUyT+jc?b<xcMIcuztR7)pmF0`2`Eq?{#lgEWEQKV5<H-K9k8d$;)+w
zqrcY8I*jJs{vDTI-v;s?0<kbW@*t^4*Th1vATRwj&xhjYPbWP}N=SI{^-BRK(<L^A
zGY8pJ4$rLH#;iFlN&UP4pTdfV9xoX_S#sMbbxTU@Xmy-1G4YxV=YoXI%e#|3+#cUv
z-B-iQ#dJ<wKkU!-x?2AQsjVMx)!**l^l9Cj)Qk_G>gGM(|NBKx`o`<`U2cCdin)7j
zW_;}GnSbVYrncUEdTh?~O`dPG%x^N^lil&*-LX2hW1+L2@@|qAdLmn9v_CW9@7V$`
zv&PuxYytBQT0LBFw{d5UVg2;`+h4zbaR2au6CWPf-CtGKH^YC{zR3;e5<irDEslIX
z<pK*ZAu}?GFyJoQAV2_0!wNc>9_&RO0;r%DeCS%yGc5v4Wkk)w=sMB!90CY1q2)ky
pt>_6H0q!v&C3kdf=m`Y@&N5?7HUZwOY>;qdV~7FTb(00e0{{{5ZKMDI
new file mode 100644
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Permission Prompts</title>
+</head>
+<body>
+  <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
+  <button id="webRTC-shareDevices" onclick="shareDevice({video: true, fake: true});">Video</button>
+  <button id="webRTC-shareMicrophone" onclick="shareDevice({audio: true, fake: true});">Audio</button>
+  <button id="webRTC-shareDevices2" onclick="shareDevice({audio: true, video: true, fake: true});">Audio and Video</button>
+  <button id="webRTC-shareScreen" onclick="shareDevice({video: {mediaSource: 'screen'}});">Screen</button>
+  <button id="web-notifications" onclick="Notification.requestPermission()">web-notifications</button>
+  <a id="addons" href="borderify.xpi">Install Add-On</a>
+  <form>
+    <input type="email" id="email" value="email@example.com" />
+    <input type="password" id="password" value="123456" />
+    <button type="submit" id="login-capture">Login</button>
+  </form>
+
+  <script type="application/javascript">
+    function shareDevice(config) {
+      navigator.mediaDevices.getUserMedia(config);
+    }
+  </script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/tools/mozscreenshots/permissionPrompts/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+  ../head.js
+
+[browser_permissionPrompts.js]
new file mode 100644
--- /dev/null
+++ b/browser/tools/mozscreenshots/permissionPrompts/browser_permissionPrompts.js
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(function* capture() {
+  if (!shouldCapture()) {
+    return;
+  }
+  let sets = ["LightweightThemes", "PermissionPrompts"];
+
+  yield TestRunner.start(sets, "permissionPrompts");
+});
--- a/browser/tools/mozscreenshots/preferences/browser_preferences.js
+++ b/browser/tools/mozscreenshots/preferences/browser_preferences.js
@@ -5,10 +5,10 @@
 "use strict";
 
 add_task(function* capture() {
   if (!shouldCapture()) {
     return;
   }
   let sets = ["Preferences"];
 
-  yield TestRunner.start(sets);
+  yield TestRunner.start(sets, "preferences");
 });
--- a/browser/tools/mozscreenshots/primaryUI/browser_primaryUI.js
+++ b/browser/tools/mozscreenshots/primaryUI/browser_primaryUI.js
@@ -5,10 +5,10 @@
 "use strict";
 
 add_task(function* capture() {
   if (!shouldCapture()) {
     return;
   }
   let sets = ["TabsInTitlebar", "Tabs", "WindowSize", "Toolbars", "LightweightThemes"];
 
-  yield TestRunner.start(sets);
+  yield TestRunner.start(sets, "primaryUI");
 });
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -31,8 +31,9 @@ pyasn1.pth:python/pyasn1
 pyasn1_modules.pth:python/pyasn1-modules
 bitstring.pth:python/bitstring
 redo.pth:python/redo
 requests.pth:python/requests
 rsa.pth:python/rsa
 futures.pth:python/futures
 ecc.pth:python/PyECC
 xpcshell.pth:testing/xpcshell
+pyyaml.pth:python/pyyaml/lib
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -12,16 +12,17 @@ const {Cc, Ci} = require("chrome");
 
 var Services = require("Services");
 var promise = require("promise");
 var EventEmitter = require("devtools/shared/event-emitter");
 var clipboard = require("sdk/clipboard");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 var {Task} = require("devtools/shared/task");
+const {initCssProperties} = require("devtools/shared/fronts/css-properties");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "ComputedViewTool", "devtools/client/inspector/computed/computed", true);
 loader.lazyRequireGetter(this, "FontInspector", "devtools/client/inspector/fonts/fonts", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
@@ -103,25 +104,24 @@ function InspectorPanel(iframeWindow, to
 }
 
 exports.InspectorPanel = InspectorPanel;
 
 InspectorPanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
-  open: function () {
-    return this.target.makeRemote().then(() => {
-      return this._getPageStyle();
-    }).then(() => {
-      return this._getDefaultNodeForSelection();
-    }).then(defaultSelection => {
-      return this._deferredOpen(defaultSelection);
-    }).then(null, console.error);
-  },
+  open: Task.async(function* () {
+    this._cssPropertiesLoaded = initCssProperties(this.toolbox);
+    yield this._cssPropertiesLoaded;
+    yield this.target.makeRemote();
+    yield this._getPageStyle();
+    let defaultSelection = yield this._getDefaultNodeForSelection();
+    return yield this._deferredOpen(defaultSelection);
+  }),
 
   get toolbox() {
     return this._toolbox;
   },
 
   get inspector() {
     return this._toolbox.inspector;
   },
@@ -644,16 +644,22 @@ InspectorPanel.prototype = {
     if (this.fontInspector) {
       this.fontInspector.destroy();
     }
 
     if (this.layoutview) {
       this.layoutview.destroy();
     }
 
+    let cssPropertiesDestroyer = this._cssPropertiesLoaded.then(({front}) => {
+      if (front) {
+        front.destroy();
+      }
+    });
+
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this._paneToggleButton.removeEventListener("mousedown",
@@ -673,17 +679,18 @@ InspectorPanel.prototype = {
     this.nodemenu = null;
     this._toolbox = null;
     this.search.destroy();
     this.search = null;
     this.searchBox = null;
 
     this._panelDestroyer = promise.all([
       sidebarDestroyer,
-      markupDestroyer
+      markupDestroyer,
+      cssPropertiesDestroyer
     ]);
 
     return this._panelDestroyer;
   },
 
   /**
    * Show the node menu.
    */
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -7,35 +7,39 @@
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const {Task} = require("devtools/shared/task");
 const {InplaceEditor, editableItem} =
       require("devtools/client/shared/inplace-editor");
 const {ReflowFront} = require("devtools/server/actors/layout");
 const {LocalizationHelper} = require("devtools/client/shared/l10n");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 const STRINGS_URI = "chrome://devtools/locale/shared.properties";
 const SHARED_L10N = new LocalizationHelper(STRINGS_URI);
 const NUMERIC = /^-?[\d\.]+$/;
 const LONG_TEXT_ROTATE_LIMIT = 3;
 
 /**
  * An instance of EditingSession tracks changes that have been made during the
  * modification of box model values. All of these changes can be reverted by
- * calling revert.
+ * calling revert. The main parameter is the LayoutView that created it.
  *
- * @param doc    A DOM document that can be used to test style rules.
- * @param rules  An array of the style rules defined for the node being edited.
- *               These should be in order of priority, least important first.
+ * @param inspector The inspector panel.
+ * @param doc       A DOM document that can be used to test style rules.
+ * @param rules     An array of the style rules defined for the node being
+ *                  edited. These should be in order of priority, least
+ *                  important first.
  */
-function EditingSession(doc, rules) {
+function EditingSession({inspector, doc, elementRules}) {
   this._doc = doc;
-  this._rules = rules;
+  this._rules = elementRules;
   this._modifications = new Map();
+  this._cssProperties = getCssProperties(inspector.toolbox);
 }
 
 EditingSession.prototype = {
   /**
    * Gets the value of a single property from the CSS rule.
    *
    * @param {StyleRuleFront} rule The CSS rule.
    * @param {String} property The name of the property.
@@ -105,17 +109,18 @@ EditingSession.prototype = {
    * @return {Promise} Resolves when the modifications are complete.
    */
   setProperties: Task.async(function* (properties) {
     for (let property of properties) {
       // Get a RuleModificationList or RuleRewriter helper object from the
       // StyleRuleActor to make changes to CSS properties.
       // Note that RuleRewriter doesn't support modifying several properties at
       // once, so we do this in a sequence here.
-      let modifications = this._rules[0].startModifyingProperties();
+      let modifications = this._rules[0].startModifyingProperties(
+        this._cssProperties);
 
       // Remember the property so it can be reverted.
       if (!this._modifications.has(property.name)) {
         this._modifications.set(property.name,
           this.getPropertyFromRule(this._rules[0], property.name));
       }
 
       // Find the index of the property to be changed, or get the next index to
@@ -138,17 +143,18 @@ EditingSession.prototype = {
   /**
    * Reverts all of the property changes made by this instance.
    * @return {Promise} Resolves when all properties have been reverted.
    */
   revert: Task.async(function* () {
     // Revert each property that we modified previously, one by one. See
     // setProperties for information about why.
     for (let [property, value] of this._modifications) {
-      let modifications = this._rules[0].startModifyingProperties();
+      let modifications = this._rules[0].startModifyingProperties(
+        this._cssProperties);
 
       // Find the index of the property to be reverted.
       let index = this.getPropertyIndex(property);
 
       if (value != "") {
         // If the property doesn't exist anymore, insert at the beginning of the
         // rule.
         if (index === -1) {
@@ -353,17 +359,17 @@ LayoutView.prototype = {
     this.reflowFront.stop();
   },
 
   /**
    * Called when the user clicks on one of the editable values in the layoutview
    */
   initEditor: function (element, event, dimension) {
     let { property } = dimension;
-    let session = new EditingSession(this.doc, this.elementRules);
+    let session = new EditingSession(this);
     let initialValue = session.getProperty(property);
 
     let editor = new InplaceEditor({
       element: element,
       initial: initialValue,
       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
       property: {
         name: dimension.property
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -9,16 +9,17 @@
 const {Cc, Ci, Cu} = require("chrome");
 const promise = require("promise");
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
 const {TextProperty} =
       require("devtools/client/inspector/rules/models/text-property");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "osString", function () {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 });
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
@@ -54,16 +55,19 @@ function Rule(elementStyle, options) {
   this.inherited = options.inherited || null;
   this.keyframes = options.keyframes || null;
   this._modificationDepth = 0;
 
   if (this.domRule && this.domRule.mediaText) {
     this.mediaText = this.domRule.mediaText;
   }
 
+  const toolbox = this.elementStyle.ruleView.inspector.toolbox;
+  this.cssProperties = getCssProperties(toolbox);
+
   // Populate the text properties with the style's current authoredText
   // value, and add in any disabled properties from the store.
   this.textProps = this._getTextProperties();
   this.textProps = this.textProps.concat(this._getDisabledProperties());
 }
 
 Rule.prototype = {
   mediaText: "",
@@ -243,17 +247,18 @@ Rule.prototype = {
       disabled.delete(this.style);
     }
 
     return modifications.apply().then(() => {
       let cssProps = {};
       // Note that even though StyleRuleActors normally provide parsed
       // declarations already, _applyPropertiesNoAuthored is only used when
       // connected to older backend that do not provide them. So parse here.
-      for (let cssProp of parseDeclarations(this.style.authoredText)) {
+      for (let cssProp of parseDeclarations(this.cssProperties.isKnown,
+                                            this.style.authoredText)) {
         cssProps[cssProp.name] = cssProp;
       }
 
       for (let textProp of this.textProps) {
         if (!textProp.enabled) {
           continue;
         }
         let cssProp = cssProps[textProp.name];
@@ -307,17 +312,18 @@ Rule.prototype = {
    * @return {Promise} a promise which will resolve when the edit
    *        is complete
    */
   applyProperties: function (modifier) {
     // If there is already a pending modification, we have to wait
     // until it settles before applying the next modification.
     let resultPromise =
         promise.resolve(this._applyingModifications).then(() => {
-          let modifications = this.style.startModifyingProperties();
+          let modifications = this.style.startModifyingProperties(
+            this.cssProperties);
           modifier(modifications);
           if (this.style.canSetRuleText) {
             return this._applyPropertiesAuthored(modifications);
           }
           return this._applyPropertiesNoAuthored(modifications);
         }).then(() => {
           this.elementStyle.markOverriddenAll();
 
@@ -383,17 +389,17 @@ Rule.prototype = {
    * @param {TextProperty} property
    *        The property which value will be previewed
    * @param {String} value
    *        The value to be used for the preview
    * @param {String} priority
    *        The property's priority (either "important" or an empty string).
    */
   previewPropertyValue: function (property, value, priority) {
-    let modifications = this.style.startModifyingProperties();
+    let modifications = this.style.startModifyingProperties(this.cssProperties);
     modifications.setProperty(this.textProps.indexOf(property),
                               property.name, value, priority);
     modifications.apply().then(() => {
       // Ensure dispatching a ruleview-changed event
       // also for previews
       this.elementStyle._changed();
     });
   },
@@ -439,17 +445,18 @@ Rule.prototype = {
    */
   _getTextProperties: function () {
     let textProps = [];
     let store = this.elementStyle.store;
 
     // Starting with FF49, StyleRuleActors provide parsed declarations.
     let props = this.style.declarations;
     if (!props) {
-      props = parseDeclarations(this.style.authoredText, true);
+      props = parseDeclarations(this.cssProperties.isKnown,
+                                this.style.authoredText, true);
     }
 
     for (let prop of props) {
       let name = prop.name;
       // If the authored text has an invalid property, it will show up
       // as nameless.  Skip these as we don't currently have a good
       // way to display them.
       if (!name) {
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const {escapeCSSComment} = require("devtools/shared/css-parsing-utils");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 /**
@@ -44,16 +45,19 @@ function TextProperty(rule, name, value,
                       invisible = false) {
   this.rule = rule;
   this.name = name;
   this.value = value;
   this.priority = priority;
   this.enabled = !!enabled;
   this.invisible = invisible;
   this.updateComputed();
+
+  const toolbox = this.rule.elementStyle.ruleView.inspector.toolbox;
+  this.cssProperties = getCssProperties(toolbox);
 }
 
 TextProperty.prototype = {
   /**
    * Update the editor associated with this text property,
    * if any.
    */
   updateEditor: function () {
@@ -181,25 +185,17 @@ TextProperty.prototype = {
   },
 
   /**
    * See whether this property's name is known.
    *
    * @return {Boolean} true if the property name is known, false otherwise.
    */
   isKnownProperty: function () {
-    try {
-      // If the property name is invalid, the cssPropertyIsShorthand
-      // will throw an exception.  But if it is valid, no exception will
-      // be thrown; so we just ignore the return value.
-      domUtils.cssPropertyIsShorthand(this.name);
-      return true;
-    } catch (e) {
-      return false;
-    }
+    return this.cssProperties.isKnown(this.name);
   },
 
   /**
    * Validate this property. Does it make sense for this value to be assigned
    * to this property name?
    *
    * @return {Boolean} true if the property value is valid, false otherwise.
    */
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -437,17 +437,17 @@ RuleEditor.prototype = {
       destroy: this._newPropertyDestroy,
       advanceChars: ":",
       contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
       popup: this.ruleView.popup
     });
 
     // Auto-close the input if multiple rules get pasted into new property.
     this.editor.input.addEventListener("paste",
-      blurOnMultipleProperties, false);
+      blurOnMultipleProperties(this.rule.cssProperties), false);
   },
 
   /**
    * Called when the new property input has been dismissed.
    *
    * @param {String} value
    *        The value in the editor.
    * @param {Boolean} commit
@@ -457,17 +457,18 @@ RuleEditor.prototype = {
     if (!value || !commit) {
       return;
     }
 
     // parseDeclarations allows for name-less declarations, but in the present
     // case, we're creating a new declaration, it doesn't make sense to accept
     // these entries
     this.multipleAddedProperties =
-      parseDeclarations(value, true).filter(d => d.name);
+      parseDeclarations(this.rule.cssProperties.isKnown, value, true)
+      .filter(d => d.name);
 
     // Blur the editor field now and deal with adding declarations later when
     // the field gets destroyed (see _newPropertyDestroy)
     this.editor.input.blur();
   },
 
   /**
    * Called when the new property editor is destroyed.
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Ci} = require("chrome");
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 const {InplaceEditor, editableField} =
       require("devtools/client/shared/inplace-editor");
 const {
   createChild,
   appendText,
   advanceValidate,
   blurOnMultipleProperties,
   throttle
@@ -39,16 +40,19 @@ function TextPropertyEditor(ruleEditor, 
   this.ruleView = this.ruleEditor.ruleView;
   this.doc = this.ruleEditor.doc;
   this.popup = this.ruleView.popup;
   this.prop = property;
   this.prop.editor = this;
   this.browserWindow = this.doc.defaultView.top;
   this._populatedComputed = false;
 
+  const toolbox = this.ruleView.inspector.toolbox;
+  this.cssProperties = getCssProperties(toolbox);
+
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
   this._onSwatchCommit = this._onSwatchCommit.bind(this);
   this._onSwatchPreview = this._onSwatchPreview.bind(this);
   this._onSwatchRevert = this._onSwatchRevert.bind(this);
@@ -194,17 +198,17 @@ TextPropertyEditor.prototype = {
         destroy: this.update,
         advanceChars: ":",
         contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
         popup: this.popup
       });
 
       // Auto blur name field on multiple CSS rules get pasted in.
       this.nameContainer.addEventListener("paste",
-        blurOnMultipleProperties, false);
+        blurOnMultipleProperties(this.cssProperties), false);
 
       this.valueContainer.addEventListener("click", (event) => {
         // Clicks within the value shouldn't propagate any further.
         event.stopPropagation();
 
         // Forward clicks on valueContainer to the editable valueSpan
         if (event.target === this.valueContainer) {
           this.valueSpan.click();
@@ -554,17 +558,17 @@ TextPropertyEditor.prototype = {
     if (!this.prop.value &&
         direction !== Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
       this.remove(direction);
       return;
     }
 
     // Adding multiple rules inside of name field overwrites the current
     // property with the first, then adds any more onto the property list.
-    let properties = parseDeclarations(value);
+    let properties = parseDeclarations(this.cssProperties.isKnown, value);
 
     if (properties.length) {
       this.prop.setName(properties[0].name);
       this.committed.name = this.prop.name;
 
       if (!this.prop.enabled) {
         this.prop.setEnabled(true);
       }
@@ -613,17 +617,18 @@ TextPropertyEditor.prototype = {
    *        The value contained in the editor.
    * @param {Boolean} commit
    *        True if the change should be applied.
    * @param {Number} direction
    *        The move focus direction number.
    */
   _onValueDone: function (value = "", commit, direction) {
     let parsedProperties = this._getValueAndExtraProperties(value);
-    let val = parseSingleValue(parsedProperties.firstValue);
+    let val = parseSingleValue(this.cssProperties.isKnown,
+                               parsedProperties.firstValue);
     let isValueUnchanged = (!commit && !this.ruleEditor.isEditing) ||
                            !parsedProperties.propertiesToAdd.length &&
                            this.committed.value === val.value &&
                            this.committed.priority === val.priority;
     // If the value is not empty and unchanged, revert the property back to
     // its original value and enabled or disabled state
     if (value.trim() && isValueUnchanged) {
       this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
@@ -701,17 +706,17 @@ TextPropertyEditor.prototype = {
   _getValueAndExtraProperties: function (value) {
     // The inplace editor will prevent manual typing of multiple properties,
     // but we need to deal with the case during a paste event.
     // Adding multiple properties inside of value editor sets value with the
     // first, then adds any more onto the property list (below this property).
     let firstValue = value;
     let propertiesToAdd = [];
 
-    let properties = parseDeclarations(value);
+    let properties = parseDeclarations(this.cssProperties.isKnown, value);
 
     // Check to see if the input string can be parsed as multiple properties
     if (properties.length) {
       // Get the first property value (if any), and any remaining
       // properties (if any)
       if (!properties[0].name && properties[0].value) {
         firstValue = properties[0].value;
         propertiesToAdd = properties.slice(1);
@@ -740,17 +745,17 @@ TextPropertyEditor.prototype = {
    */
   _previewValue: function (value, reverting = false) {
     // Since function call is throttled, we need to make sure we are still
     // editing, and any selector modifications have been completed
     if (!reverting && (!this.editing || this.ruleEditor.isEditing)) {
       return;
     }
 
-    let val = parseSingleValue(value);
+    let val = parseSingleValue(this.cssProperties.isKnown, value);
     this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
                                               val.priority);
   },
 
   /**
    * Validate this property. Does it make sense for this value to be assigned
    * to this property name? This does not apply the property value
    *
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -127,23 +127,25 @@ function throttle(func, wait, scope) {
 }
 
 exports.throttle = throttle;
 
 /**
  * Event handler that causes a blur on the target if the input has
  * multiple CSS properties as the value.
  */
-function blurOnMultipleProperties(e) {
-  setTimeout(() => {
-    let props = parseDeclarations(e.target.value);
-    if (props.length > 1) {
-      e.target.blur();
-    }
-  }, 0);
+function blurOnMultipleProperties(cssProperties) {
+  return (e) => {
+    setTimeout(() => {
+      let props = parseDeclarations(cssProperties.isKnown, e.target.value);
+      if (props.length > 1) {
+        e.target.blur();
+      }
+    }, 0);
+  };
 }
 
 exports.blurOnMultipleProperties = blurOnMultipleProperties;
 
 /**
  * Log the provided error to the console and return a rejected Promise for
  * this error.
  *
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/css-properties-db.js
@@ -0,0 +1,422 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This list is generated from the output of the CssPropertiesActor. If a server
+ * does not support the actor, this is loaded as a backup. This list does not
+ * guarantee that the server actually supports these CSS properties.
+ */
+exports.propertiesList = [
+  "align-content",
+  "align-items",
+  "align-self",
+  "animation-delay",
+  "animation-direction",
+  "animation-duration",
+  "animation-fill-mode",
+  "animation-iteration-count",
+  "animation-name",
+  "animation-play-state",
+  "animation-timing-function",
+  "-moz-appearance",
+  "backface-visibility",
+  "background-attachment",
+  "background-blend-mode",
+  "background-clip",
+  "background-color",
+  "background-image",
+  "background-origin",
+  "background-position-x",
+  "background-position-y",
+  "background-repeat",
+  "background-size",
+  "-moz-binding",
+  "block-size",
+  "border-block-end-color",
+  "border-block-end-style",
+  "border-block-end-width",
+  "border-block-start-color",
+  "border-block-start-style",
+  "border-block-start-width",
+  "border-bottom-color",
+  "-moz-border-bottom-colors",
+  "border-bottom-left-radius",
+  "border-bottom-right-radius",
+  "border-bottom-style",
+  "border-bottom-width",
+  "border-collapse",
+  "border-image-outset",
+  "border-image-repeat",
+  "border-image-slice",
+  "border-image-source",
+  "border-image-width",
+  "border-inline-end-color",
+  "border-inline-end-style",
+  "border-inline-end-width",
+  "border-inline-start-color",
+  "border-inline-start-style",
+  "border-inline-start-width",
+  "border-left-color",
+  "-moz-border-left-colors",
+  "border-left-style",
+  "border-left-width",
+  "border-right-color",
+  "-moz-border-right-colors",
+  "border-right-style",
+  "border-right-width",
+  "border-spacing",
+  "border-top-color",
+  "-moz-border-top-colors",
+  "border-top-left-radius",
+  "border-top-right-radius",
+  "border-top-style",
+  "border-top-width",
+  "bottom",
+  "-moz-box-align",
+  "box-decoration-break",
+  "-moz-box-direction",
+  "-moz-box-flex",
+  "-moz-box-ordinal-group",
+  "-moz-box-orient",
+  "-moz-box-pack",
+  "box-shadow",
+  "box-sizing",
+  "caption-side",
+  "clear",
+  "clip",
+  "clip-path",
+  "clip-rule",
+  "color",
+  "color-adjust",
+  "color-interpolation",
+  "color-interpolation-filters",
+  "-moz-column-count",
+  "-moz-column-fill",
+  "-moz-column-gap",
+  "-moz-column-rule-color",
+  "-moz-column-rule-style",
+  "-moz-column-rule-width",
+  "-moz-column-width",
+  "content",
+  "-moz-control-character-visibility",
+  "counter-increment",
+  "counter-reset",
+  "cursor",
+  "direction",
+  "display",
+  "dominant-baseline",
+  "empty-cells",
+  "fill",
+  "fill-opacity",
+  "fill-rule",
+  "filter",
+  "flex-basis",
+  "flex-direction",
+  "flex-grow",
+  "flex-shrink",
+  "flex-wrap",
+  "float",
+  "-moz-float-edge",
+  "flood-color",
+  "flood-opacity",
+  "font-family",
+  "font-feature-settings",
+  "font-kerning",
+  "font-language-override",
+  "font-size",
+  "font-size-adjust",
+  "font-stretch",
+  "font-style",
+  "font-synthesis",
+  "font-variant-alternates",
+  "font-variant-caps",
+  "font-variant-east-asian",
+  "font-variant-ligatures",
+  "font-variant-numeric",
+  "font-variant-position",
+  "font-weight",
+  "-moz-force-broken-image-icon",
+  "grid-auto-columns",
+  "grid-auto-flow",
+  "grid-auto-rows",
+  "grid-column-end",
+  "grid-column-gap",
+  "grid-column-start",
+  "grid-row-end",
+  "grid-row-gap",
+  "grid-row-start",
+  "grid-template-areas",
+  "grid-template-columns",
+  "grid-template-rows",
+  "height",
+  "hyphens",
+  "image-orientation",
+  "-moz-image-region",
+  "image-rendering",
+  "ime-mode",
+  "inline-size",
+  "isolation",
+  "justify-content",
+  "justify-items",
+  "justify-self",
+  "left",
+  "letter-spacing",
+  "lighting-color",
+  "line-height",
+  "list-style-image",
+  "list-style-position",
+  "list-style-type",
+  "margin-block-end",
+  "margin-block-start",
+  "margin-bottom",
+  "margin-inline-end",
+  "margin-inline-start",
+  "margin-left",
+  "margin-right",
+  "margin-top",
+  "marker-end",
+  "marker-mid",
+  "marker-offset",
+  "marker-start",
+  "mask",
+  "mask-type",
+  "max-block-size",
+  "max-height",
+  "max-inline-size",
+  "max-width",
+  "min-block-size",
+  "min-height",
+  "min-inline-size",
+  "min-width",
+  "mix-blend-mode",
+  "object-fit",
+  "object-position",
+  "offset-block-end",
+  "offset-block-start",
+  "offset-inline-end",
+  "offset-inline-start",
+  "opacity",
+  "order",
+  "-moz-orient",
+  "-moz-osx-font-smoothing",
+  "outline-color",
+  "outline-offset",
+  "-moz-outline-radius-bottomleft",
+  "-moz-outline-radius-bottomright",
+  "-moz-outline-radius-topleft",
+  "-moz-outline-radius-topright",
+  "outline-style",
+  "outline-width",
+  "overflow-x",
+  "overflow-y",
+  "padding-block-end",
+  "padding-block-start",
+  "padding-bottom",
+  "padding-inline-end",
+  "padding-inline-start",
+  "padding-left",
+  "padding-right",
+  "padding-top",
+  "page-break-after",
+  "page-break-before",
+  "page-break-inside",
+  "paint-order",
+  "perspective",
+  "perspective-origin",
+  "pointer-events",
+  "position",
+  "quotes",
+  "resize",
+  "right",
+  "ruby-align",
+  "ruby-position",
+  "scroll-behavior",
+  "scroll-snap-coordinate",
+  "scroll-snap-destination",
+  "scroll-snap-points-x",
+  "scroll-snap-points-y",
+  "scroll-snap-type-x",
+  "scroll-snap-type-y",
+  "shape-rendering",
+  "-moz-stack-sizing",
+  "stop-color",
+  "stop-opacity",
+  "stroke",
+  "stroke-dasharray",
+  "stroke-dashoffset",
+  "stroke-linecap",
+  "stroke-linejoin",
+  "stroke-miterlimit",
+  "stroke-opacity",
+  "stroke-width",
+  "-moz-tab-size",
+  "table-layout",
+  "text-align",
+  "-moz-text-align-last",
+  "text-anchor",
+  "text-combine-upright",
+  "text-decoration-color",
+  "text-decoration-line",
+  "text-decoration-style",
+  "text-emphasis-color",
+  "text-emphasis-position",
+  "text-emphasis-style",
+  "-webkit-text-fill-color",
+  "text-indent",
+  "text-orientation",
+  "text-overflow",
+  "text-rendering",
+  "text-shadow",
+  "-moz-text-size-adjust",
+  "-webkit-text-stroke-color",
+  "-webkit-text-stroke-width",
+  "text-transform",
+  "top",
+  "transform",
+  "transform-box",
+  "transform-origin",
+  "transform-style",
+  "transition-delay",
+  "transition-duration",
+  "transition-property",
+  "transition-timing-function",
+  "unicode-bidi",
+  "-moz-user-focus",
+  "-moz-user-input",
+  "-moz-user-modify",
+  "-moz-user-select",
+  "vector-effect",
+  "vertical-align",
+  "visibility",
+  "white-space",
+  "width",
+  "will-change",
+  "-moz-window-dragging",
+  "word-break",
+  "word-spacing",
+  "word-wrap",
+  "writing-mode",
+  "z-index",
+  "all",
+  "animation",
+  "background",
+  "background-position",
+  "border",
+  "border-block-end",
+  "border-block-start",
+  "border-bottom",
+  "border-color",
+  "border-image",
+  "border-inline-end",
+  "border-inline-start",
+  "border-left",
+  "border-radius",
+  "border-right",
+  "border-style",
+  "border-top",
+  "border-width",
+  "-moz-column-rule",
+  "-moz-columns",
+  "flex",
+  "flex-flow",
+  "font",
+  "font-variant",
+  "grid",
+  "grid-area",
+  "grid-column",
+  "grid-gap",
+  "grid-row",
+  "grid-template",
+  "list-style",
+  "margin",
+  "marker",
+  "outline",
+  "-moz-outline-radius",
+  "overflow",
+  "padding",
+  "scroll-snap-type",
+  "text-decoration",
+  "text-emphasis",
+  "-webkit-text-stroke",
+  "-moz-transform",
+  "transition",
+  "-moz-transform-origin",
+  "-moz-perspective-origin",
+  "-moz-perspective",
+  "-moz-transform-style",
+  "-moz-backface-visibility",
+  "-moz-border-image",
+  "-moz-transition",
+  "-moz-transition-delay",
+  "-moz-transition-duration",
+  "-moz-transition-property",
+  "-moz-transition-timing-function",
+  "-moz-animation",
+  "-moz-animation-delay",
+  "-moz-animation-direction",
+  "-moz-animation-duration",
+  "-moz-animation-fill-mode",
+  "-moz-animation-iteration-count",
+  "-moz-animation-name",
+  "-moz-animation-play-state",
+  "-moz-animation-timing-function",
+  "-moz-box-sizing",
+  "-moz-font-feature-settings",
+  "-moz-font-language-override",
+  "-moz-padding-end",
+  "-moz-padding-start",
+  "-moz-margin-end",
+  "-moz-margin-start",
+  "-moz-border-end",
+  "-moz-border-end-color",
+  "-moz-border-end-style",
+  "-moz-border-end-width",
+  "-moz-border-start",
+  "-moz-border-start-color",
+  "-moz-border-start-style",
+  "-moz-border-start-width",
+  "-moz-hyphens",
+  "-webkit-animation",
+  "-webkit-animation-delay",
+  "-webkit-animation-direction",
+  "-webkit-animation-duration",
+  "-webkit-animation-fill-mode",
+  "-webkit-animation-iteration-count",
+  "-webkit-animation-name",
+  "-webkit-animation-play-state",
+  "-webkit-animation-timing-function",
+  "-webkit-filter",
+  "-webkit-text-size-adjust",
+  "-webkit-transform",
+  "-webkit-transform-origin",
+  "-webkit-transform-style",
+  "-webkit-backface-visibility",
+  "-webkit-perspective",
+  "-webkit-perspective-origin",
+  "-webkit-transition",
+  "-webkit-transition-delay",
+  "-webkit-transition-duration",
+  "-webkit-transition-property",
+  "-webkit-transition-timing-function",
+  "-webkit-border-radius",
+  "-webkit-border-top-left-radius",
+  "-webkit-border-top-right-radius",
+  "-webkit-border-bottom-left-radius",
+  "-webkit-border-bottom-right-radius",
+  "-webkit-background-clip",
+  "-webkit-background-origin",
+  "-webkit-background-size",
+  "-webkit-border-image",
+  "-webkit-box-shadow",
+  "-webkit-box-sizing",
+  "-webkit-box-flex",
+  "-webkit-box-ordinal-group",
+  "-webkit-box-orient",
+  "-webkit-box-direction",
+  "-webkit-box-align",
+  "-webkit-box-pack",
+  "-webkit-user-select"
+];
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -16,16 +16,17 @@ DIRS += [
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
     'css-angle.js',
     'css-color-db.js',
     'css-color.js',
+    'css-properties-db.js',
     'css-reload.js',
     'Curl.jsm',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
--- a/devtools/client/shared/test/unit/test_parseDeclarations.js
+++ b/devtools/client/shared/test/unit/test_parseDeclarations.js
@@ -3,16 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   // Simple test
   {
     input: "p:v;",
     expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
   },
   // Simple test
@@ -354,17 +355,18 @@ function run_test() {
 }
 
 // Test parseDeclarations.
 function run_basic_tests() {
   for (let test of TEST_DATA) {
     do_print("Test input string " + test.input);
     let output;
     try {
-      output = parseDeclarations(test.input, test.parseComments);
+      output = parseDeclarations(isCssPropertyKnown, test.input,
+                                 test.parseComments);
     } catch (e) {
       do_print("parseDeclarations threw an exception with the given input " +
         "string");
       if (test.throws) {
         do_print("Exception expected");
         do_check_true(true);
       } else {
         do_print("Exception unexpected\n" + e);
@@ -389,17 +391,17 @@ const COMMENT_DATA = [
     expected: []
   },
 ];
 
 // Test parseCommentDeclarations.
 function run_comment_tests() {
   for (let test of COMMENT_DATA) {
     do_print("Test input string " + test.input);
-    let output = _parseCommentDeclarations(test.input, 0,
+    let output = _parseCommentDeclarations(isCssPropertyKnown, test.input, 0,
                                            test.input.length + 4);
     deepEqual(output, test.expected);
   }
 }
 
 function assertOutput(actual, expected) {
   if (actual.length === expected.length) {
     for (let i = 0; i < expected.length; i++) {
--- a/devtools/client/shared/test/unit/test_parseSingleValue.js
+++ b/devtools/client/shared/test/unit/test_parseSingleValue.js
@@ -3,16 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {parseSingleValue} = require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   {input: null, throws: true},
   {input: undefined, throws: true},
   {input: "", expected: {value: "", priority: ""}},
   {input: "  \t \t \n\n  ", expected: {value: "", priority: ""}},
   {input: "blue", expected: {value: "blue", priority: ""}},
   {input: "blue !important", expected: {value: "blue", priority: "important"}},
@@ -64,17 +65,17 @@ const TEST_DATA = [
     }
   }
 ];
 
 function run_test() {
   for (let test of TEST_DATA) {
     do_print("Test input value " + test.input);
     try {
-      let output = parseSingleValue(test.input);
+      let output = parseSingleValue(isCssPropertyKnown, test.input);
       assertOutput(output, test.expected);
     } catch (e) {
       do_print("parseSingleValue threw an exception with the given input " +
         "value");
       if (test.throws) {
         do_print("Exception expected");
         do_check_true(true);
       } else {
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -3,16 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 Cu.import("resource://devtools/shared/Loader.jsm");
 const {RuleRewriter} = devtools.require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   {
     desc: "simple set",
     input: "p:v;",
     instruction: {type: "set", name: "p", value: "N", priority: "",
                   index: 0},
     expected: "p:N;"
@@ -438,17 +439,17 @@ const TEST_DATA = [
     instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
                   index: 1},
     expected: "/*! no: semicolon */\nwalrus: zebra;\n",
     changed: {}
   },
 ];
 
 function rewriteDeclarations(inputString, instruction, defaultIndentation) {
-  let rewriter = new RuleRewriter(null, inputString);
+  let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
   rewriter.defaultIndentation = defaultIndentation;
 
   switch (instruction.type) {
     case "rename":
       rewriter.renameProperty(instruction.index, instruction.name,
                               instruction.newName);
       break;
 
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/css-properties.js
@@ -0,0 +1,53 @@
+/* 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 { Cc, Ci, Cu } = require("chrome");
+
+loader.lazyGetter(this, "DOMUtils", () => {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
+
+const protocol = require("devtools/shared/protocol");
+const { ActorClassWithSpec, Actor } = protocol;
+const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
+
+var CssPropertiesActor = exports.CssPropertiesActor = ActorClassWithSpec(cssPropertiesSpec, {
+  typeName: "cssProperties",
+
+  initialize: function(conn, parent) {
+    Actor.prototype.initialize.call(this, conn);
+    this.parent = parent;
+  },
+
+  destroy: function() {
+    Actor.prototype.destroy.call(this);
+  },
+
+  getCSSDatabase: function() {
+    const propertiesList = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
+    return { propertiesList };
+  }
+});
+
+/**
+ * Test if a CSS is property is known using server-code.
+ *
+ * @param {string} name
+ * @return {Boolean}
+ */
+function isCssPropertyKnown(name) {
+  try {
+    // If the property name is unknown, the cssPropertyIsShorthand
+    // will throw an exception.  But if it is known, no exception will
+    // be thrown; so we just ignore the return value.
+    DOMUtils.cssPropertyIsShorthand(name);
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+exports.isCssPropertyKnown = isCssPropertyKnown
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -16,16 +16,17 @@ DevToolsModules(
     'animation.js',
     'breakpoint.js',
     'call-watcher.js',
     'canvas.js',
     'child-process.js',
     'childtab.js',
     'chrome.js',
     'common.js',
+    'css-properties.js',
     'csscoverage.js',
     'device.js',
     'director-manager.js',
     'director-registry.js',
     'environment.js',
     'errordocs.js',
     'eventlooplag.js',
     'frame.js',
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -5,16 +5,17 @@
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {getDefinedGeometryProperties} = require("devtools/server/actors/highlighters/geometry-editor");
 const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 const {Task} = require("devtools/shared/task");
 const events = require("sdk/event/core");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
 const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets");
 const {pageStyleSpec, styleRuleSpec} = require("devtools/shared/specs/styles");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
@@ -1088,18 +1089,19 @@ var StyleRuleActor = protocol.ActorClass
         form.keyText = this.rawRule.keyText || "";
         break;
     }
 
     // Parse the text into a list of declarations so the client doesn't have to
     // and so that we can safely determine if a declaration is valid rather than
     // have the client guess it.
     if (form.authoredText || form.cssText) {
-      let declarations = parseDeclarations(form.authoredText ||
-                                           form.cssText, true);
+      let declarations = parseDeclarations(isCssPropertyKnown,
+                                           form.authoredText || form.cssText,
+                                           true);
       form.declarations = declarations.map(decl => {
         decl.isValid = DOMUtils.cssPropertyIsValid(decl.name, decl.value);
         return decl;
       });
     }
 
     return form;
   },
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -520,16 +520,21 @@ var DebuggerServer = {
       constructor: "EventLoopLagActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/layout", {
       prefix: "reflow",
       constructor: "ReflowActor",
       type: { tab: true }
     });
+    this.registerModule("devtools/server/actors/css-properties", {
+      prefix: "cssProperties",
+      constructor: "CssPropertiesActor",
+      type: { tab: true }
+    });
     this.registerModule("devtools/server/actors/csscoverage", {
       prefix: "cssUsage",
       constructor: "CSSUsageActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/monitor", {
       prefix: "monitor",
       constructor: "MonitorActor",
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -3,16 +3,17 @@ tags = devtools
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   animation-data.html
   Debugger.Source.prototype.element.js
   Debugger.Source.prototype.element-2.js
   Debugger.Source.prototype.element.html
   director-helpers.js
   hello-actor.js
+  inspector_css-properties.html
   inspector_getImageData.html
   inspector-delay-image-response.sjs
   inspector-helpers.js
   inspector-search-data.html
   inspector-styles-data.css
   inspector-styles-data.html
   inspector-traversal-data.html
   large-image.jpg
@@ -27,16 +28,17 @@ support-files =
 [test_connection-manager.html]
 skip-if = buildapp == 'mulet'
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
 [test_css-logic.html]
 [test_css-logic-inheritance.html]
 [test_css-logic-media-queries.html]
 [test_css-logic-specificity.html]
+[test_css-properties.html]
 [test_Debugger.Source.prototype.introductionScript.html]
 [test_Debugger.Source.prototype.introductionType.html]
 [test_Debugger.Source.prototype.element.html]
 [test_Debugger.Script.prototype.global.html]
 [test_device.html]
 skip-if = buildapp == 'mulet'
 [test_director.html]
 [test_director_connectToChild.html]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector_css-properties.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<body>
+  <script type="text/javascript">
+    window.onload = function() {
+      window.opener.postMessage('ready', '*');
+    };
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-properties.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test CSS Properties Actor</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+window.onload = function() {
+  const { initCssProperties, getCssProperties } =
+    require("devtools/shared/fronts/css-properties");
+
+  function promiseAttachUrl (url) {
+    return new Promise((resolve, reject) => {
+      attachURL(url, function(err, client, tab, doc) {
+        if (err) {
+          return reject(err);
+        }
+        resolve({client, tab, doc});
+      });
+    })
+  }
+
+  const runCssPropertiesTests = Task.async(function* (url, useActor) {
+    info("Opening two tabs.");
+
+    let attachmentA = yield promiseAttachUrl(url);
+    let attachmentB = yield promiseAttachUrl(url);
+
+    const toolboxMockA = {
+      target: {
+        hasActor: () => useActor,
+        client: attachmentA.client,
+        form: attachmentA.tab
+      }
+    };
+    const toolboxMockB = {
+      target: {
+        hasActor: () => useActor,
+        client: attachmentB.client,
+        form: attachmentB.tab
+      }
+    };
+
+    yield initCssProperties(toolboxMockA);
+    yield initCssProperties(toolboxMockB);
+
+    const cssProperties = getCssProperties(toolboxMockA);
+    const cssPropertiesA = getCssProperties(toolboxMockA);
+    const cssPropertiesB = getCssProperties(toolboxMockB);
+
+    is(cssProperties, cssPropertiesA,
+       "Multiple calls with the same toolbox returns the same object.");
+    isnot(cssProperties, cssPropertiesB,
+       "Multiple calls with the different toolboxes return different "+
+       " objects.");
+
+    ok(cssProperties.isKnown("border"),
+      "The `border` shorthand property is known.");
+    ok(cssProperties.isKnown("display"),
+      "The `display` property is known.");
+    ok(!cssProperties.isKnown("foobar"),
+      "A fake property is not known.");
+    ok(cssProperties.isKnown("--foobar"),
+      "A CSS variable properly evaluates.");
+    ok(cssProperties.isKnown("--foob\\{ar"),
+      "A CSS variable with escaped character properly evaluates.");
+    ok(cssProperties.isKnown("--fübar"),
+      "A CSS variable unicode properly evaluates.");
+    ok(!cssProperties.isKnown("--foo bar"),
+      "A CSS variable with spaces fails");
+  });
+
+  addAsyncTest(function* setup() {
+    let url = document.getElementById("cssProperties").href;
+    yield runCssPropertiesTests(url, true);
+    yield runCssPropertiesTests(url, false);
+
+    runNextTest();
+  });
+
+  SimpleTest.waitForExplicitFinish();
+  runNextTest();
+}
+  </script>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265798">Mozilla Bug 1265798</a>
+  <a id="cssProperties" target="_blank" href="inspector_css-properties.html">Test Document</a>
+</body>
+</html>
--- a/devtools/server/tests/mochitest/test_styles-modify.html
+++ b/devtools/server/tests/mochitest/test_styles-modify.html
@@ -7,16 +7,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug </title>
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 const inspector = require("devtools/server/actors/inspector");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 window.onload = function() {
   SimpleTest.waitForExplicitFinish();
   runNextTest();
 }
 
 var gWalker = null;
 var gStyles = null;
@@ -77,23 +78,23 @@ addAsyncTest(function* modifyProperties(
   is(elementStyle.cssText, "", "Should have expected cssText");
   is(elementStyle.cssText, localNode.style.cssText,
      "Local node and style front match.");
 
   runNextTest();
 });
 
 function* setProperty(rule, index, name, value) {
-  let changes = rule.startModifyingProperties();
+  let changes = rule.startModifyingProperties(isCssPropertyKnown);
   changes.setProperty(index, name, value);
   yield changes.apply();
 }
 
 function* removeProperty(rule, index, name) {
-  let changes = rule.startModifyingProperties();
+  let changes = rule.startModifyingProperties(isCssPropertyKnown);
   changes.removeProperty(index, name);
   yield changes.apply();
 }
 
 addTest(function cleanup() {
   delete gStyles;
   delete gWalker;
   delete gClient;
--- a/devtools/shared/css-parsing-utils.js
+++ b/devtools/shared/css-parsing-utils.js
@@ -9,24 +9,20 @@
 // parseDeclarations - parse a CSS rule into declarations
 // RuleRewriter - rewrite CSS rule text
 // parsePseudoClassesAndAttributes - parse selector and extract
 //     pseudo-classes
 // parseSingleValue - parse a single CSS property value
 
 "use strict";
 
-const {Cc, Ci} = require("chrome");
 loader.lazyRequireGetter(this, "CSS", "CSS");
 const promise = require("promise");
 const {getCSSLexer} = require("devtools/shared/css-lexer");
 const {Task} = require("devtools/shared/task");
-loader.lazyGetter(this, "DOMUtils", () => {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
 
 const SELECTOR_ATTRIBUTE = exports.SELECTOR_ATTRIBUTE = 1;
 const SELECTOR_ELEMENT = exports.SELECTOR_ELEMENT = 2;
 const SELECTOR_PSEUDO_CLASS = exports.SELECTOR_PSEUDO_CLASS = 3;
 
 // Used to test whether a newline appears anywhere in some text.
 const NEWLINE_RX = /[\r\n]/;
 // Used to test whether a bit of text starts an empty comment, either
@@ -146,52 +142,35 @@ function escapeCSSComment(inputString) {
  * @return {String} the un-escaped result
  */
 function unescapeCSSComment(inputString) {
   let result = inputString.replace(/\/\\(\\*)\*/g, "/$1*");
   return result.replace(/\*\\(\\*)\//g, "*$1/");
 }
 
 /**
- * A helper function for parseDeclarations that implements a heuristic
- * to decide whether a given bit of comment text should be parsed as a
- * declaration.
- *
- * @param {String} name the property name that has been parsed
- * @return {Boolean} true if the property should be parsed, false if
- *                        the remainder of the comment should be skipped
- */
-function shouldParsePropertyInComment(name) {
-  try {
-    // If the property name is invalid, the cssPropertyIsShorthand
-    // will throw an exception.  But if it is valid, no exception will
-    // be thrown; so we just ignore the return value.
-    DOMUtils.cssPropertyIsShorthand(name);
-    return true;
-  } catch (e) {
-    return false;
-  }
-}
-
-/**
  * A helper function for @see parseDeclarations that handles parsing
  * of comment text.  This wraps a recursive call to parseDeclarations
  * with the processing needed to ensure that offsets in the result
  * refer back to the original, unescaped, input string.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
  * @param {String} commentText The text of the comment, without the
  *                             delimiters.
  * @param {Number} startOffset The offset of the comment opener
  *                             in the original text.
  * @param {Number} endOffset The offset of the comment closer
  *                           in the original text.
  * @return {array} Array of declarations of the same form as returned
  *                 by parseDeclarations.
  */
-function parseCommentDeclarations(commentText, startOffset, endOffset) {
+function parseCommentDeclarations(isCssPropertyKnown, commentText, startOffset,
+                                  endOffset) {
   let commentOverride = false;
   if (commentText === "") {
     return [];
   } else if (commentText[0] === COMMENT_PARSING_HEURISTIC_BYPASS_CHAR) {
     // This is the special sign that the comment was written by
     // rewriteDeclarations and so we should bypass the usual
     // heuristic.
     commentOverride = true;
@@ -237,18 +216,18 @@ function parseCommentDeclarations(commen
       ++rewrites[i];
     }
   }
 
   // Note that we pass "false" for parseComments here.  It doesn't
   // seem worthwhile to support declarations in comments-in-comments
   // here, as there's no way to generate those using the tools, and
   // users would be crazy to write such things.
-  let newDecls = parseDeclarationsInternal(rewrittenText, false,
-                                           true, commentOverride);
+  let newDecls = parseDeclarationsInternal(isCssPropertyKnown, rewrittenText,
+                                           false, true, commentOverride);
   for (let decl of newDecls) {
     decl.offsets[0] = rewrites[decl.offsets[0]];
     decl.offsets[1] = rewrites[decl.offsets[1]];
     decl.colonOffsets[0] = rewrites[decl.colonOffsets[0]];
     decl.colonOffsets[1] = rewrites[decl.colonOffsets[1]];
     decl.commentOffsets = [startOffset, endOffset];
   }
   return newDecls;
@@ -271,29 +250,31 @@ function getEmptyDeclaration() {
 /**
  * A helper function that does all the parsing work for
  * parseDeclarations.  This is separate because it has some arguments
  * that don't make sense in isolation.
  *
  * The return value and arguments are like parseDeclarations, with
  * these additional arguments.
  *
+ * @param {Function} isCssPropertyKnown
+ *        Function to check if the CSS property is known.
  * @param {Boolean} inComment
  *        If true, assume that this call is parsing some text
  *        which came from a comment in another declaration.
  *        In this case some heuristics are used to avoid parsing
  *        text which isn't obviously a series of declarations.
  * @param {Boolean} commentOverride
  *        This only makes sense when inComment=true.
  *        When true, assume that the comment was generated by
  *        rewriteDeclarations, and skip the usual name-checking
  *        heuristic.
  */
-function parseDeclarationsInternal(inputString, parseComments,
-                                   inComment, commentOverride) {
+function parseDeclarationsInternal(isCssPropertyKnown, inputString,
+                                   parseComments, inComment, commentOverride) {
   if (inputString === null || inputString === undefined) {
     throw new Error("empty input string");
   }
 
   let lexer = getCSSLexer(inputString);
 
   let declarations = [getEmptyDeclaration()];
   let lastProp = declarations[0];
@@ -330,17 +311,17 @@ function parseDeclarationsInternal(input
         lastProp.name = current.trim();
         lastProp.colonOffsets = [token.startOffset, token.endOffset];
         current = "";
         hasBang = false;
 
         // When parsing a comment body, if the left-hand-side is not a
         // valid property name, then drop it and stop parsing.
         if (inComment && !commentOverride &&
-            !shouldParsePropertyInComment(lastProp.name)) {
+            !isCssPropertyKnown(lastProp.name)) {
           lastProp.name = null;
           break;
         }
       } else {
         // Otherwise, just append ':' to the current value (declaration value
         // with colons)
         current += ":";
       }
@@ -373,17 +354,18 @@ function parseDeclarationsInternal(input
     } else if (token.tokenType === "whitespace") {
       if (current !== "") {
         current += " ";
       }
     } else if (token.tokenType === "comment") {
       if (parseComments && !lastProp.name && !lastProp.value) {
         let commentText = inputString.substring(token.startOffset + 2,
                                                 token.endOffset - 2);
-        let newDecls = parseCommentDeclarations(commentText, token.startOffset,
+        let newDecls = parseCommentDeclarations(isCssPropertyKnown, commentText,
+                                                token.startOffset,
                                                 token.endOffset);
 
         // Insert the new declarations just before the final element.
         let lastDecl = declarations.pop();
         declarations = [...declarations, ...newDecls, lastDecl];
       }
     } else {
       current += inputString.substring(token.startOffset, token.endOffset);
@@ -415,23 +397,28 @@ function parseDeclarationsInternal(input
   // Remove declarations that have neither a name nor a value
   declarations = declarations.filter(prop => prop.name || prop.value);
 
   return declarations;
 }
 
 /**
  * Returns an array of CSS declarations given a string.
- * For example, parseDeclarations("width: 1px; height: 1px") would return
+ * For example, parseDeclarations(isCssPropertyKnown, "width: 1px; height: 1px")
+ * would return:
  * [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
  *
  * The input string is assumed to only contain declarations so { and }
  * characters will be treated as part of either the property or value,
  * depending where it's found.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server.
  * @param {String} inputString
  *        An input string of CSS
  * @param {Boolean} parseComments
  *        If true, try to parse the contents of comments as well.
  *        A comment will only be parsed if it occurs outside of
  *        the body of some other declaration.
  * @return {Array} an array of objects with the following signature:
  *         [{"name": string, "value": string, "priority": string,
@@ -445,60 +432,71 @@ function parseDeclarationsInternal(input
  *         usually "" to mean no additional termination is needed.
  *         "colonOffsets" holds the start and end locations of the
  *         ":" that separates the property name from the value.
  *         If the declaration appears in a comment, then there will
  *         be an additional {"commentOffsets": [start, end] property
  *         on the object, which will hold the offsets of the start
  *         and end of the enclosing comment.
  */
-function parseDeclarations(inputString, parseComments = false) {
-  return parseDeclarationsInternal(inputString, parseComments, false, false);
+function parseDeclarations(isCssPropertyKnown, inputString,
+                           parseComments = false) {
+  return parseDeclarationsInternal(isCssPropertyKnown, inputString,
+                                   parseComments, false, false);
 }
 
 /**
  * Return an object that can be used to rewrite declarations in some
  * source text.  The source text and parsing are handled in the same
  * way as @see parseDeclarations, with |parseComments| being true.
  * Rewriting is done by calling one of the modification functions like
  * setPropertyEnabled.  The returned object has the same interface
  * as @see RuleModificationList.
  *
  * An example showing how to disable the 3rd property in a rule:
  *
- *    let rewriter = new RuleRewriter(ruleActor, ruleActor.authoredText);
+ *    let rewriter = new RuleRewriter(isCssPropertyKnown, ruleActor,
+ *                                    ruleActor.authoredText);
  *    rewriter.setPropertyEnabled(3, "color", false);
  *    rewriter.apply().then(() => { ... the change is made ... });
  *
  * The exported rewriting methods are |renameProperty|, |setPropertyEnabled|,
  * |createProperty|, |setProperty|, and |removeProperty|.  The |apply|
  * method can be used to send the edited text to the StyleRuleActor;
  * |getDefaultIndentation| is useful for the methods requiring a
  * default indentation value; and |getResult| is useful for testing.
  *
  * Additionally, editing will set the |changedDeclarations| property
  * on this object.  This property has the same form as the |changed|
  * property of the object returned by |getResult|.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server. Note that if Bug 1222047
+ *        is completed then isCssPropertyKnown will not need to be passed in.
+ *        The CssProperty front will be able to obtained directly from the
+ *        RuleRewriter.
  * @param {StyleRuleFront} rule The style rule to use.  Note that this
  *        is only needed by the |apply| and |getDefaultIndentation| methods;
  *        and in particular for testing it can be |null|.
  * @param {String} inputString The CSS source text to parse and modify.
  * @return {Object} an object that can be used to rewrite the input text.
  */
-function RuleRewriter(rule, inputString) {
+function RuleRewriter(isCssPropertyKnown, rule, inputString) {
   this.rule = rule;
   this.inputString = inputString;
   // Whether there are any newlines in the input text.
   this.hasNewLine = /[\r\n]/.test(this.inputString);
   // Keep track of which any declarations we had to rewrite while
   // performing the requested action.
   this.changedDeclarations = {};
   // The declarations.
-  this.declarations = parseDeclarations(this.inputString, true);
+  this.declarations = parseDeclarations(isCssPropertyKnown, this.inputString,
+                                        true);
 
   this.decl = null;
   this.result = null;
   // If not null, a promise that must be wait upon before |apply| can
   // do its work.
   this.editPromise = null;
 
   // If the |defaultIndentation| property is set, then it is used;
@@ -1077,22 +1075,27 @@ function parsePseudoClassesAndAttributes
 
   return result;
 }
 
 /**
  * Expects a single CSS value to be passed as the input and parses the value
  * and priority.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server.
  * @param {String} value
  *        The value from the text editor.
  * @return {Object} an object with 'value' and 'priority' properties.
  */
-function parseSingleValue(value) {
-  let declaration = parseDeclarations("a: " + value + ";")[0];
+function parseSingleValue(isCssPropertyKnown, value) {
+  let declaration = parseDeclarations(isCssPropertyKnown,
+                                      "a: " + value + ";")[0];
   return {
     value: declaration ? declaration.value : "",
     priority: declaration ? declaration.priority : ""
   };
 }
 
 exports.cssTokenizer = cssTokenizer;
 exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/css-properties.js
@@ -0,0 +1,128 @@
+/* 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 { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
+const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
+const { Task } = require("devtools/shared/task");
+
+/**
+ * Build up a regular expression that matches a CSS variable token. This is an
+ * ident token that starts with two dashes "--".
+ *
+ * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ */
+var NON_ASCII = "[^\\x00-\\x7F]";
+var ESCAPE = "\\\\[^\n\r]";
+var FIRST_CHAR = ["[_a-z]", NON_ASCII, ESCAPE].join("|");
+var TRAILING_CHAR = ["[_a-z0-9-]", NON_ASCII, ESCAPE].join("|");
+var IS_VARIABLE_TOKEN = new RegExp(`^--(${FIRST_CHAR})(${TRAILING_CHAR})*$`,
+                                   "i");
+/**
+ * Check that this is a CSS variable.
+ *
+ * @param {String} input
+ * @return {Boolean}
+ */
+function isCssVariable(input) {
+  return !!input.match(IS_VARIABLE_TOKEN);
+}
+
+var cachedCssProperties = new WeakMap();
+
+/**
+ * The CssProperties front provides a mechanism to have a one-time asynchronous
+ * load of a CSS properties database. This is then fed into the CssProperties
+ * interface that provides synchronous methods for finding out what CSS
+ * properties the current server supports.
+ */
+const CssPropertiesFront = FrontClassWithSpec(cssPropertiesSpec, {
+  initialize: function (client, { cssPropertiesActor }) {
+    Front.prototype.initialize.call(this, client, {actor: cssPropertiesActor});
+    this.manage(this);
+  }
+});
+
+exports.CssPropertiesFront = CssPropertiesFront;
+
+/**
+ * Ask questions to a CSS database. This class does not care how the database
+ * gets loaded in, only the questions that you can ask to it.
+ *
+ * @param {array} properties
+ *                A list of all supported CSS properties.
+ */
+function CssProperties(properties) {
+  this.properties = properties;
+  // Bind isKnown so it can be passed around to helper functions.
+  this.isKnown = this.isKnown.bind(this);
+}
+
+CssProperties.prototype = {
+  /**
+   * Checks to see if the property is known by the browser. This function has
+   * `this` already bound so that it can be passed around by reference.
+   *
+   * @param {String}   property
+   *                   The property name to be checked.
+   * @return {Boolean}
+   */
+  isKnown(property) {
+    return this.properties.includes(property) || isCssVariable(property);
+  }
+};
+
+exports.CssProperties = CssProperties;
+
+/**
+ * Create a CssProperties object with a fully loaded CSS database. The
+ * CssProperties interface can be queried synchronously, but the initialization
+ * is potentially async and should be handled up-front when the tool is created.
+ *
+ * The front is returned only with this function so that it can be destroyed
+ * once the toolbox is destroyed.
+ *
+ * @param {Toolbox} The current toolbox.
+ * @returns {Promise} Resolves to {cssProperties, cssPropertiesFront}.
+ */
+exports.initCssProperties = Task.async(function* (toolbox) {
+  let client = toolbox.target.client;
+  if (cachedCssProperties.has(client)) {
+    return cachedCssProperties.get(client);
+  }
+
+  let propertiesList, front;
+
+  // Get the list dynamically if the cssProperties exists.
+  if (toolbox.target.hasActor("cssProperties")) {
+    front = CssPropertiesFront(client, toolbox.target.form);
+    const db = yield front.getCSSDatabase();
+    propertiesList = db.propertiesList;
+  } else {
+    // The target does not support this actor, so require a static list of
+    // supported properties.
+    const db = require("devtools/client/shared/css-properties-db");
+    propertiesList = db.propertiesList;
+  }
+  const cssProperties = new CssProperties(propertiesList);
+  cachedCssProperties.set(client, {cssProperties, front});
+  return {cssProperties, front};
+});
+
+/**
+ * Synchronously get a cached and initialized CssProperties.
+ *
+ * @param {Toolbox} The current toolbox.
+ * @returns {CssProperties}
+ */
+exports.getCssProperties = function (toolbox) {
+  if (!cachedCssProperties.has(toolbox.target.client)) {
+    throw new Error("The CSS database has not been initialized, please make " +
+                    "sure initCssDatabase was called once before for this " +
+                    "toolbox.");
+  }
+  return cachedCssProperties.get(toolbox.target.client).cssProperties;
+};
+
+exports.CssPropertiesFront = CssPropertiesFront;
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -2,14 +2,15 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'addons.js',
     'animation.js',
+    'css-properties.js',
     'highlighters.js',
     'inspector.js',
     'storage.js',
     'styles.js',
     'stylesheets.js'
 )
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -123,20 +123,24 @@ const StyleRuleFront = FrontClassWithSpe
     this._form.column = column;
   }),
 
   /**
    * Return a new RuleModificationList or RuleRewriter for this node.
    * A RuleRewriter will be returned when the rule's canSetRuleText
    * trait is true; otherwise a RuleModificationList will be
    * returned.
+   *
+   * @param {CssPropertiesFront} cssProperties
+   *                             This is needed by the RuleRewriter.
+   * @return {RuleModificationList}
    */
-  startModifyingProperties: function () {
+  startModifyingProperties: function (cssProperties) {
     if (this.canSetRuleText) {
-      return new RuleRewriter(this, this.authoredText);
+      return new RuleRewriter(cssProperties.isKnown, this, this.authoredText);
     }
     return new RuleModificationList(this);
   },
 
   get type() {
     return this._form.type;
   },
   get line() {
@@ -411,9 +415,8 @@ var RuleModificationList = Class({
    * @param {String} value value of the new property
    * @param {String} priority priority of the new property; either
    *                          the empty string or "important"
    */
   createProperty: function () {
     // Nothing.
   },
 });
-
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/css-properties.js
@@ -0,0 +1,19 @@
+/* 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 { RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const cssPropertiesSpec = generateActorSpec({
+  typeName: "cssProperties",
+
+  methods: {
+    getCSSDatabase: {
+      request: {},
+      response: RetVal("json"),
+    }
+  }
+});
+
+exports.cssPropertiesSpec = cssPropertiesSpec;
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -2,15 +2,16 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'addons.js',
     'animation.js',
+    'css-properties.js',
     'highlighters.js',
     'inspector.js',
     'storage.js',
     'styleeditor.js',
     'styles.js',
     'stylesheets.js'
 )
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5221,16 +5221,18 @@ pref("browser.addon-watch.ignore", "[\"m
 pref("browser.addon-watch.percentage-limit", 5);
 
 // Search service settings
 pref("browser.search.log", false);
 pref("browser.search.update", true);
 pref("browser.search.update.log", false);
 pref("browser.search.update.interval", 21600);
 pref("browser.search.suggest.enabled", true);
+pref("browser.search.reset.enabled", false);
+pref("browser.search.reset.whitelist", "");
 pref("browser.search.geoSpecificDefaults", false);
 pref("browser.search.geoip.url", "https://location.services.mozilla.com/v1/country?key=%MOZILLA_API_KEY%");
 // NOTE: this timeout figure is also the "high" value for the telemetry probe
 // SEARCH_SERVICE_COUNTRY_FETCH_MS - if you change this also change that probe.
 pref("browser.search.geoip.timeout", 2000);
 
 #ifdef MOZ_OFFICIAL_BRANDING
 // {moz:official} expands to "official"
--- a/netwerk/base/nsIBrowserSearchService.idl
+++ b/netwerk/base/nsIBrowserSearchService.idl
@@ -432,16 +432,22 @@ interface nsIBrowserSearchService : nsIS
    * profile directory, it will be removed from disk.
    *
    * @param  engine
    *         The engine to remove.
    */
   void removeEngine(in nsISearchEngine engine);
 
   /**
+   * The original Engine object that is the default for this region,
+   * ignoring changes the user may have subsequently made.
+   */
+  readonly attribute nsISearchEngine originalDefaultEngine;
+
+  /**
    * Alias for the currentEngine attribute, kept for add-on compatibility.
    */
   attribute nsISearchEngine defaultEngine;
 
   /**
    * The currently active search engine.
    * Unless the application doesn't ship any search plugin, this should never
    * be null. If the currently active engine is removed, this attribute will
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -164,31 +164,16 @@ var LoginManagerParent = {
           recipes,
         });
       } catch (e) {
         log("error sending message to target", e);
       }
       return;
     }
 
-    let allLoginsCount = Services.logins.countLogins(formOrigin, "", null);
-    // If there are no logins for this site, bail out now.
-    if (!allLoginsCount) {
-      try {
-        target.sendAsyncMessage("RemoteLogins:loginsFound", {
-          requestId: requestId,
-          logins: [],
-          recipes,
-        });
-      } catch (e) {
-        log("error sending message to target", e);
-      }
-      return;
-    }
-
     // If we're currently displaying a master password prompt, defer
     // processing this form until the user handles the prompt.
     if (Services.logins.uiBusy) {
       log("deferring sendLoginDataToChild for", formOrigin);
       let self = this;
       let observer = {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                                Ci.nsISupportsWeakReference]),
@@ -226,26 +211,16 @@ var LoginManagerParent = {
     // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
     // doesn't support structured cloning.
     var jsLogins = LoginHelper.loginsToVanillaObjects(logins);
     target.sendAsyncMessage("RemoteLogins:loginsFound", {
       requestId: requestId,
       logins: jsLogins,
       recipes,
     });
-
-    const PWMGR_FORM_ACTION_EFFECT =  Services.telemetry.getHistogramById("PWMGR_FORM_ACTION_EFFECT");
-    if (logins.length == 0) {
-      PWMGR_FORM_ACTION_EFFECT.add(2);
-    } else if (logins.length == allLoginsCount) {
-      PWMGR_FORM_ACTION_EFFECT.add(0);
-    } else {
-      // logins.length < allLoginsCount
-      PWMGR_FORM_ACTION_EFFECT.add(1);
-    }
   }),
 
   doAutocompleteSearch: function({ formOrigin, actionOrigin,
                                    searchString, previousResult,
                                    rect, requestId, remote }, target) {
     // Note: previousResult is a regular object, not an
     // nsIAutoCompleteResult.
     var result;
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -2403,35 +2403,68 @@ Engine.prototype = {
       type = "application/x-moz-phonesearch";
     }
 
     delete this._defaultMobileResponseType;
     return this._defaultMobileResponseType = type;
   },
 #endif
 
+  get _isWhiteListed() {
+    let url = this._getURLOfType(URLTYPE_SEARCH_HTML).template;
+    let hostname = makeURI(url).host;
+    let whitelist = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+                            .getCharPref("reset.whitelist")
+                            .split(",");
+    if (whitelist.includes(hostname)) {
+      LOG("The hostname " + hostname + " is white listed, " +
+          "we won't show the search reset prompt");
+      return true;
+    }
+
+    return false;
+  },
+
   // from nsISearchEngine
   getSubmission: function SRCH_ENG_getSubmission(aData, aResponseType, aPurpose) {
 #ifdef ANDROID
     if (!aResponseType) {
       aResponseType = this._defaultMobileResponseType;
     }
 #endif
     if (!aResponseType) {
       aResponseType = URLTYPE_SEARCH_HTML;
     }
 
+    if (aResponseType == URLTYPE_SEARCH_HTML &&
+        Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).getBoolPref("reset.enabled") &&
+        this.name == Services.search.currentEngine.name &&
+        !this._isDefault &&
+        (!this.getAttr("loadPathHash") ||
+         this.getAttr("loadPathHash") != getVerificationHash(this._loadPath)) &&
+        !this._isWhiteListed) {
+      let url = "about:searchreset";
+      let data = [];
+      if (aData)
+        data.push("data=" + encodeURIComponent(aData));
+      if (aPurpose)
+        data.push("purpose=" + aPurpose);
+      if (data.length)
+        url += "?" + data.join("&");
+      return new Submission(makeURI(url));
+    }
+
     var url = this._getURLOfType(aResponseType);
 
     if (!url)
       return null;
 
     if (!aData) {
       // Return a dummy submission object with our searchForm attribute
-      return new Submission(makeURI(this._getSearchFormWithPurpose(aPurpose)), null);
+      return new Submission(makeURI(this._getSearchFormWithPurpose(aPurpose)));
     }
 
     LOG("getSubmission: In data: \"" + aData + "\"; Purpose: \"" + aPurpose + "\"");
     var data = "";
     try {
       data = gTextToSubURI.ConvertAndEscape(this.queryCharset, aData);
     } catch (ex) {
       LOG("getSubmission: Falling back to default queryCharset!");
@@ -2817,17 +2850,17 @@ SearchService.prototype = {
   get _sortedEngines() {
     if (!this.__sortedEngines)
       return this._buildSortedEngineList();
     return this.__sortedEngines;
   },
 
   // Get the original Engine object that is the default for this region,
   // ignoring changes the user may have subsequently made.
-  get _originalDefaultEngine() {
+  get originalDefaultEngine() {
     let defaultEngine = this.getVerifiedGlobalAttr("searchDefault");
     if (!defaultEngine) {
       let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
       let nsIPLS = Ci.nsIPrefLocalizedString;
 
       let defPref = getGeoSpecificPrefName("defaultenginename");
       try {
         defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
@@ -2836,17 +2869,17 @@ SearchService.prototype = {
         // getEngineByName will just return null, which is the best we can do.
       }
     }
 
     return this.getEngineByName(defaultEngine);
   },
 
   resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() {
-    this.currentEngine = this._originalDefaultEngine;
+    this.currentEngine = this.originalDefaultEngine;
   },
 
   _buildCache: function SRCH_SVC__buildCache() {
     if (this._batchTask)
       this._batchTask.disarm();
 
     TelemetryStopwatch.start("SEARCH_SERVICE_BUILD_CACHE_MS");
     let cache = {};
@@ -3943,16 +3976,17 @@ SearchService.prototype = {
     if (!aTemplate)
       FAIL("Invalid template passed to addEngineWithDetails!");
     if (this._engines[aName])
       FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);
 
     var engine = new Engine(sanitizeName(aName), false);
     engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
                              aMethod, aTemplate, aExtensionID);
+    engine._loadPath = "[other]addEngineWithDetails";
     this._addEngineToStore(engine);
   },
 
   addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
                                          aConfirm, aCallback) {
     LOG("addEngine: Adding \"" + aEngineURL + "\".");
     this._ensureInitialized();
     try {
@@ -4106,23 +4140,23 @@ SearchService.prototype = {
       if (engine && (this.getGlobalAttr("hash") == getVerificationHash(name) ||
                      engine._isDefault)) {
         // If the current engine is a default one, we can relax the
         // verification hash check to reduce the annoyance for users who
         // backup/sync their profile in custom ways.
         this._currentEngine = engine;
       }
       if (!name)
-        this._currentEngine = this._originalDefaultEngine;
+        this._currentEngine = this.originalDefaultEngine;
     }
 
     // If the current engine is not set or hidden, we fallback...
     if (!this._currentEngine || this._currentEngine.hidden) {
       // first to the original default engine
-      let originalDefault = this._originalDefaultEngine;
+      let originalDefault = this.originalDefaultEngine;
       if (!originalDefault || originalDefault.hidden) {
         // then to the first visible engine
         let firstVisible = this._getSortedEngines(false)[0];
         if (firstVisible && !firstVisible.hidden) {
           this.currentEngine = firstVisible;
           return firstVisible;
         }
         // and finally as a last resort we unhide the original default engine.
@@ -4149,19 +4183,21 @@ SearchService.prototype = {
     // handle both.
     if (!(val instanceof Ci.nsISearchEngine) && !(val instanceof Engine))
       FAIL("Invalid argument passed to currentEngine setter");
 
     var newCurrentEngine = this.getEngineByName(val.name);
     if (!newCurrentEngine)
       FAIL("Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED);
 
-    if (!newCurrentEngine._isDefault && newCurrentEngine._loadPath) {
+    if (!newCurrentEngine._isDefault) {
       // If a non default engine is being set as the current engine, ensure
       // its loadPath has a verification hash.
+      if (!newCurrentEngine._loadPath)
+        newCurrentEngine._loadPath = "[other]unknown";
       let loadPathHash = getVerificationHash(newCurrentEngine._loadPath);
       let currentHash = newCurrentEngine.getAttr("loadPathHash");
       if (!currentHash || currentHash != loadPathHash) {
         newCurrentEngine.setAttr("loadPathHash", loadPathHash);
         notifyAction(newCurrentEngine, SEARCH_ENGINE_CHANGED);
       }
     }
 
@@ -4171,17 +4207,17 @@ SearchService.prototype = {
     this._currentEngine = newCurrentEngine;
 
     // If we change the default engine in the future, that change should impact
     // users who have switched away from and then back to the build's "default"
     // engine. So clear the user pref when the currentEngine is set to the
     // build's default engine, so that the currentEngine getter falls back to
     // whatever the default is.
     let newName = this._currentEngine.name;
-    if (this._currentEngine == this._originalDefaultEngine) {
+    if (this._currentEngine == this.originalDefaultEngine) {
       newName = "";
     }
 
     this.setGlobalAttr("current", newName);
     this.setGlobalAttr("hash", getVerificationHash(newName));
 
     notifyAction(this._currentEngine, SEARCH_ENGINE_DEFAULT);
     notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_addEngineWithDetails.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kSearchEngineID = "addEngineWithDetails_test_engine";
+const kSearchEngineURL = "http://example.com/?search={searchTerms}";
+const kSearchTerm = "foo";
+
+add_task(function* test_addEngineWithDetails() {
+  do_check_false(Services.search.isInitialized);
+
+  Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+          .setBoolPref("reset.enabled", true);
+
+  yield asyncInit();
+
+  Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
+                                       kSearchEngineURL);
+
+  // An engine added with addEngineWithDetails should have a load path, even
+  // though we can't point to a specific file.
+  let engine = Services.search.getEngineByName(kSearchEngineID);
+  do_check_eq(engine.wrappedJSObject._loadPath, "[other]addEngineWithDetails");
+
+  // Set the engine as default; this should set a loadPath verification hash,
+  // which should ensure we don't show the search reset prompt.
+  Services.search.currentEngine = engine;
+
+  let expectedURL = kSearchEngineURL.replace("{searchTerms}", kSearchTerm);
+  let submission =
+    Services.search.currentEngine.getSubmission(kSearchTerm, null, "searchbar");
+  do_check_eq(submission.uri.spec, expectedURL);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_searchReset.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
+
+const kTestEngineShortName = "engine";
+const kWhiteListPrefName = "reset.whitelist";
+
+function run_test() {
+  // Copy an engine to [profile]/searchplugin/
+  let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile);
+  if (!dir.exists())
+    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  do_get_file("data/engine.xml").copyTo(dir, kTestEngineShortName + ".xml");
+
+  let file = dir.clone();
+  file.append(kTestEngineShortName + ".xml");
+  do_check_true(file.exists());
+
+  do_check_false(Services.search.isInitialized);
+
+  Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+          .setBoolPref("reset.enabled", true);
+
+  run_next_test();
+}
+
+function* removeLoadPathHash() {
+  // Remove the loadPathHash and re-initialize the search service.
+  let cache = yield promiseCacheData();
+  for (let engine of cache.engines) {
+    if (engine._shortName == kTestEngineShortName) {
+      delete engine._metaData["loadPathHash"];
+      break;
+    }
+  }
+  yield promiseSaveCacheData(cache);
+  yield asyncReInit();
+}
+
+add_task(function* test_no_prompt_when_valid_loadPathHash() {
+  yield asyncInit();
+
+  // test the engine is loaded ok.
+  let engine = Services.search.getEngineByName(kTestEngineName);
+  do_check_neq(engine, null);
+
+  yield promiseAfterCache();
+
+  // The test engine has been found in the profile directory and imported,
+  // so it shouldn't have a loadPathHash.
+  let metadata = yield promiseEngineMetadata();
+  do_check_true(kTestEngineShortName in metadata);
+  do_check_false("loadPathHash" in metadata[kTestEngineShortName]);
+
+  // After making it the currentEngine with the search service API,
+  // the test engine should have a valid loadPathHash.
+  Services.search.currentEngine = engine;
+  yield promiseAfterCache();
+  metadata = yield promiseEngineMetadata();
+  do_check_true("loadPathHash" in metadata[kTestEngineShortName]);
+  let loadPathHash = metadata[kTestEngineShortName].loadPathHash;
+  do_check_eq(typeof loadPathHash, "string");
+  do_check_eq(loadPathHash.length, 44);
+
+  // A search should not cause the search reset prompt.
+  let submission =
+    Services.search.currentEngine.getSubmission("foo", null, "searchbar");
+  do_check_eq(submission.uri.spec,
+              "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t");
+});
+
+add_task(function* test_promptURLs() {
+  yield removeLoadPathHash();
+
+  // The default should still be the test engine.
+  let currentEngine = Services.search.currentEngine;
+  do_check_eq(currentEngine.name, kTestEngineName);
+  // but the submission url should be about:searchreset
+  let url = (data, purpose) =>
+    currentEngine.getSubmission(data, null, purpose).uri.spec;
+  do_check_eq(url("foo", "searchbar"),
+              "about:searchreset?data=foo&purpose=searchbar");
+  do_check_eq(url("foo"), "about:searchreset?data=foo");
+  do_check_eq(url("", "searchbar"), "about:searchreset?purpose=searchbar");
+  do_check_eq(url(""), "about:searchreset");
+  do_check_eq(url("", ""), "about:searchreset");
+
+  // Calling the currentEngine setter for the same engine should
+  // prevent further prompts.
+  Services.search.currentEngine = Services.search.currentEngine;
+  do_check_eq(url("foo", "searchbar"),
+              "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t");
+
+  // And the loadPathHash should be back.
+  yield promiseAfterCache();
+  let metadata = yield promiseEngineMetadata();
+  do_check_true("loadPathHash" in metadata[kTestEngineShortName]);
+  let loadPathHash = metadata[kTestEngineShortName].loadPathHash;
+  do_check_eq(typeof loadPathHash, "string");
+  do_check_eq(loadPathHash.length, 44);
+});
+
+add_task(function* test_whitelist() {
+  yield removeLoadPathHash();
+
+  // The default should still be the test engine.
+  let currentEngine = Services.search.currentEngine;
+  do_check_eq(currentEngine.name, kTestEngineName);
+  let expectPrompt = shouldPrompt => {
+    let expectedURL =
+      shouldPrompt ? "about:searchreset?data=foo&purpose=searchbar"
+                   : "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t";
+    let url = currentEngine.getSubmission("foo", null, "searchbar").uri.spec;
+    do_check_eq(url, expectedURL);
+  };
+  expectPrompt(true);
+
+  // Unless we whitelist our test engine.
+  let branch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
+  let initialWhiteList = branch.getCharPref(kWhiteListPrefName);
+  branch.setCharPref(kWhiteListPrefName, "example.com,test.tld");
+  expectPrompt(true);
+  branch.setCharPref(kWhiteListPrefName, "www.google.com");
+  expectPrompt(false);
+  branch.setCharPref(kWhiteListPrefName, "example.com,www.google.com,test.tld");
+  expectPrompt(false);
+
+  // The loadPathHash should not be back after the prompt was skipped due to the
+  // whitelist.
+  yield asyncReInit();
+  let metadata = yield promiseEngineMetadata();
+  do_check_false("loadPathHash" in metadata[kTestEngineShortName]);
+
+  branch.setCharPref(kWhiteListPrefName, initialWhiteList);
+  expectPrompt(true);
+});
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -89,8 +89,10 @@ tags = addons
 [test_remove_profile_engine.js]
 [test_selectedEngine.js]
 [test_geodefaults.js]
 [test_hidden.js]
 [test_currentEngine_fallback.js]
 [test_require_engines_in_cache.js]
 [test_update_telemetry.js]
 [test_svg_icon.js]
+[test_searchReset.js]
+[test_addEngineWithDetails.js]
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5513,16 +5513,25 @@
   },
   "SEARCH_COUNTS": {
     "expires_in_version": "never",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
     "description": "Record the search counts for search engines"
   },
+  "SEARCH_RESET_RESULT": {
+    "alert_emails": ["fqueze@mozilla.com"],
+    "bug_numbers": [1203168],
+    "expires_in_version": "53",
+    "kind": "enumerated",
+    "n_values": 5,
+    "releaseChannelCollection": "opt-out",
+    "description": "Result of showing the search reset prompt to the user. 0=restored original default, 1=kept current engine, 2=changed engine, 3=closed the page"
+  },
   "SEARCH_SERVICE_INIT_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 1000,
     "n_buckets": 15,
     "description": "Time (ms) it takes to initialize the search service"
   },
   "SEARCH_SERVICE_INIT_SYNC": {
@@ -9265,22 +9274,16 @@
   },
   "PWMGR_BLOCKLIST_NUM_SITES": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 100,
     "n_buckets" : 10,
     "description": "The number of sites for which the user has explicitly rejected saving logins"
   },
-  "PWMGR_FORM_ACTION_EFFECT": {
-    "expires_in_version": "never",
-    "kind": "enumerated",
-    "n_values" : 5,
-    "description": "The effect of the form action on signon autofill. (0=No effect, 1=Fewer logins after considering the form action, 2=No logins match form origin and action."
-  },
   "PWMGR_FORM_AUTOFILL_RESULT": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values" : 20,
     "description": "The result of auto-filling a login form. See http://mzl.la/1Mbs6jL for bucket descriptions."
   },
   "PWMGR_LOGIN_LAST_USED_DAYS": {
     "expires_in_version": "never",
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -821,17 +821,16 @@
     "PREDICTOR_TOTAL_PRECONNECTS_UNUSED",
     "PREDICTOR_TOTAL_PRECONNECTS_USED",
     "PREDICTOR_TOTAL_PREDICTIONS",
     "PREDICTOR_TOTAL_PRERESOLVES",
     "PREDICTOR_WAIT_TIME",
     "PROCESS_CRASH_SUBMIT_ATTEMPT",
     "PROCESS_CRASH_SUBMIT_SUCCESS",
     "PWMGR_BLOCKLIST_NUM_SITES",
-    "PWMGR_FORM_ACTION_EFFECT",
     "PWMGR_FORM_AUTOFILL_RESULT",
     "PWMGR_LOGIN_LAST_USED_DAYS",
     "PWMGR_LOGIN_PAGE_SAFETY",
     "PWMGR_MANAGE_COPIED_PASSWORD",
     "PWMGR_MANAGE_COPIED_USERNAME",
     "PWMGR_MANAGE_DELETED",
     "PWMGR_MANAGE_DELETED_ALL",
     "PWMGR_MANAGE_OPENED",
@@ -2036,17 +2035,16 @@
     "PUSH_API_SUBSCRIBE_HTTP2_TIME",
     "PUSH_API_SUBSCRIBE_SUCCEEDED",
     "PUSH_API_SUBSCRIBE_WS_TIME",
     "PUSH_API_UNSUBSCRIBE_ATTEMPT",
     "PUSH_API_UNSUBSCRIBE_FAILED",
     "PUSH_API_UNSUBSCRIBE_SUCCEEDED",
     "PUSH_API_USED",
     "PWMGR_BLOCKLIST_NUM_SITES",
-    "PWMGR_FORM_ACTION_EFFECT",
     "PWMGR_FORM_AUTOFILL_RESULT",
     "PWMGR_LOGIN_LAST_USED_DAYS",
     "PWMGR_LOGIN_PAGE_SAFETY",
     "PWMGR_MANAGE_COPIED_PASSWORD",
     "PWMGR_MANAGE_COPIED_USERNAME",
     "PWMGR_MANAGE_DELETED",
     "PWMGR_MANAGE_DELETED_ALL",
     "PWMGR_MANAGE_OPENED",
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -1384,18 +1384,18 @@ add_task(function* test_defaultSearchEng
   data = TelemetryEnvironment.currentEnvironment;
   checkEnvironmentData(data);
 
   const EXPECTED_SEARCH_ENGINE = "other-" + SEARCH_ENGINE_ID;
   Assert.equal(data.settings.defaultSearchEngine, EXPECTED_SEARCH_ENGINE);
 
   const EXPECTED_SEARCH_ENGINE_DATA = {
     name: "telemetry_default",
-    loadPath: null,
-    origin: "unverified"
+    loadPath: "[other]addEngineWithDetails",
+    origin: "verified"
   };
   Assert.deepEqual(data.settings.defaultSearchEngineData, EXPECTED_SEARCH_ENGINE_DATA);
   TelemetryEnvironment.unregisterChangeListener("testWatch_SearchDefault");
 
   // Cleanly install an engine from an xml file, and check if origin is
   // recorded as "verified".
   gNow = fakeNow(futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE));
   let promise = new Promise(resolve => {
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -170,45 +170,49 @@ html|button {
   padding: 3px;
   /* override forms.css */
   font: inherit;
 }
 
 /* xul buttons and menulists */
 
 *|button,
+html|select,
 xul|colorpicker[type="button"],
 xul|menulist {
   -moz-appearance: none;
   min-height: 30px;
   color: var(--in-content-text-color);
   border: 1px solid var(--in-content-box-border-color);
   -moz-border-top-colors: none !important;
   -moz-border-right-colors: none !important;
   -moz-border-bottom-colors: none !important;
   -moz-border-left-colors: none !important;
   border-radius: 2px;
   background-color: var(--in-content-page-background);
 }
 
 html|button:enabled:hover,
+html|select:enabled:hover,
 xul|button:not([disabled="true"]):hover,
 xul|colorpicker[type="button"]:not([disabled="true"]):hover,
 xul|menulist:not([disabled="true"]):hover {
   background-color: var(--in-content-box-background-hover);
 }
 
 html|button:enabled:hover:active,
+html|select:enabled:hover:active,
 xul|button:not([disabled="true"]):hover:active,
 xul|colorpicker[type="button"]:not([disabled="true"]):hover:active,
 xul|menulist[open="true"]:not([disabled="true"]) {
   background-color: var(--in-content-box-background-active);
 }
 
 html|button:disabled,
+html|select:disabled,
 xul|button[disabled="true"],
 xul|colorpicker[type="button"][disabled="true"],
 xul|menulist[disabled="true"] {
   opacity: 0.5;
 }
 
 *|button.primary {
   background-color: var(--in-content-primary-button-background);