Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 27 May 2016 14:43:24 -0700
changeset 338405 5c73a69cb9eb5aab005d31d601f69abaa792390a
parent 338404 643f808c71a13f100163ff22d58d9a7113a2dc2f (current diff)
parent 338301 ea15028498ed95677844fb7f30be5efcaf8b2621 (diff)
child 338406 94308fa7a31673a90a9741f16a1dd7a6f268bafc
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
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound, a=merge
browser/base/content/browser.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/security/sandbox/mac/Sandbox.mm
+++ b/security/sandbox/mac/Sandbox.mm
@@ -17,24 +17,127 @@
 
 // XXX There are currently problems with the /usr/include/sandbox.h file on
 // some/all of the Macs in Mozilla's build system.  For the time being (until
 // this problem is resolved), we refer directly to what we need from it,
 // rather than including it here.
 extern "C" int sandbox_init(const char *profile, uint64_t flags, char **errorbuf);
 extern "C" void sandbox_free_error(char *errorbuf);
 
+#define MAC_OS_X_VERSION_10_0_HEX  0x00001000
+#define MAC_OS_X_VERSION_10_6_HEX  0x00001060
+#define MAC_OS_X_VERSION_10_7_HEX  0x00001070
+#define MAC_OS_X_VERSION_10_8_HEX  0x00001080
+#define MAC_OS_X_VERSION_10_9_HEX  0x00001090
+#define MAC_OS_X_VERSION_10_10_HEX 0x000010A0
+
+// Note about "major", "minor" and "bugfix" in the following code:
+//
+// The code decomposes an OS X version number into these components, and in
+// doing so follows Apple's terminology in Gestalt.h.  But this is very
+// misleading, because in other contexts Apple uses the "minor" component of
+// an OS X version number to indicate a "major" release (for example the "9"
+// in OS X 10.9.5), and the "bugfix" component to indicate a "minor" release
+// (for example the "5" in OS X 10.9.5).
+
+class OSXVersion {
+public:
+  static bool OnLionOrLater();
+  static int32_t OSXVersionMinor();
+
+private:
+  static void GetSystemVersion(int32_t& aMajor, int32_t& aMinor, int32_t& aBugFix);
+  static int32_t GetVersionNumber();
+  static int32_t mOSXVersion;
+};
+
+int32_t OSXVersion::mOSXVersion = -1;
+
+bool OSXVersion::OnLionOrLater()
+{
+  return (GetVersionNumber() >= MAC_OS_X_VERSION_10_7_HEX);
+}
+
+int32_t OSXVersion::OSXVersionMinor()
+{
+  return (GetVersionNumber() & 0xF0) >> 4;
+}
+
+void
+OSXVersion::GetSystemVersion(int32_t& aMajor, int32_t& aMinor, int32_t& aBugFix)
+{
+  SInt32 major = 0, minor = 0, bugfix = 0;
+
+  CFURLRef url =
+    CFURLCreateWithString(kCFAllocatorDefault,
+                          CFSTR("file:///System/Library/CoreServices/SystemVersion.plist"),
+                          NULL);
+  CFReadStreamRef stream =
+    CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
+  CFReadStreamOpen(stream);
+  CFDictionaryRef sysVersionPlist = (CFDictionaryRef)
+    CFPropertyListCreateWithStream(kCFAllocatorDefault,
+                                   stream, 0, kCFPropertyListImmutable,
+                                   NULL, NULL);
+  CFReadStreamClose(stream);
+  CFRelease(stream);
+  CFRelease(url);
+
+  CFStringRef versionString = (CFStringRef)
+    CFDictionaryGetValue(sysVersionPlist, CFSTR("ProductVersion"));
+  CFArrayRef versions =
+    CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault,
+                                           versionString, CFSTR("."));
+  CFIndex count = CFArrayGetCount(versions);
+  if (count > 0) {
+    CFStringRef component = (CFStringRef) CFArrayGetValueAtIndex(versions, 0);
+    major = CFStringGetIntValue(component);
+    if (count > 1) {
+      component = (CFStringRef) CFArrayGetValueAtIndex(versions, 1);
+      minor = CFStringGetIntValue(component);
+      if (count > 2) {
+        component = (CFStringRef) CFArrayGetValueAtIndex(versions, 2);
+        bugfix = CFStringGetIntValue(component);
+      }
+    }
+  }
+  CFRelease(sysVersionPlist);
+  CFRelease(versions);
+
+  // If 'major' isn't what we expect, assume the oldest version of OS X we
+  // currently support (OS X 10.6).
+  if (major != 10) {
+    aMajor = 10; aMinor = 6; aBugFix = 0;
+  } else {
+    aMajor = major; aMinor = minor; aBugFix = bugfix;
+  }
+}
+
+int32_t
+OSXVersion::GetVersionNumber()
+{
+  if (mOSXVersion == -1) {
+    int32_t major, minor, bugfix;
+    GetSystemVersion(major, minor, bugfix);
+    mOSXVersion = MAC_OS_X_VERSION_10_0_HEX + (minor << 4) + bugfix;
+  }
+  return mOSXVersion;
+}
+
 namespace mozilla {
 
 static const char pluginSandboxRules[] =
   "(version 1)\n"
   "(deny default)\n"
   "(allow signal (target self))\n"
   "(allow sysctl-read)\n"
-  "(allow iokit-open (iokit-user-client-class \"IOHIDParamUserClient\"))\n"
+  // Illegal syntax on OS X 10.6, needed on 10.7 and up.
+  "%s(allow iokit-open (iokit-user-client-class \"IOHIDParamUserClient\"))\n"
+  // Needed only on OS X 10.6
+  "%s(allow file-read-data (literal \"%s\"))\n"
   "(allow mach-lookup\n"
   "    (global-name \"com.apple.cfprefsd.agent\")\n"
   "    (global-name \"com.apple.cfprefsd.daemon\")\n"
   "    (global-name \"com.apple.system.opendirectoryd.libinfo\")\n"
   "    (global-name \"com.apple.system.logger\")\n"
   "    (global-name \"com.apple.ls.boxd\"))\n"
   "(allow file-read*\n"
   "    (regex #\"^/etc$\")\n"
@@ -49,316 +152,328 @@ static const char pluginSandboxRules[] =
 
 static const char widevinePluginSandboxRulesAddend[] =
   "(allow mach-lookup (global-name \"com.apple.windowserver.active\"))\n";
 
 static const char contentSandboxRules[] =
   "(version 1)\n"
   "\n"
   "(define sandbox-level %d)\n"
+  "(define macosMinorVersion %d)\n"
   "(define appPath \"%s\")\n"
   "(define appBinaryPath \"%s\")\n"
   "(define appDir \"%s\")\n"
   "(define appTempDir \"%s\")\n"
   "(define home-path \"%s\")\n"
   "\n"
   "(import \"/System/Library/Sandbox/Profiles/system.sb\")\n"
   "\n"
-  "(begin\n"
-  "  (deny default)\n"
-  "  (debug deny)\n"
-  "\n"
-  "  (define resolving-literal literal)\n"
-  "  (define resolving-subpath subpath)\n"
-  "  (define resolving-regex regex)\n"
-  "\n"
-  "  (define container-path appPath)\n"
-  "  (define appdir-path appDir)\n"
-  "  (define var-folders-re \"^/private/var/folders/[^/][^/]\")\n"
-  "  (define var-folders2-re (string-append var-folders-re \"/[^/]+/[^/]\"))\n"
+  "(if \n"
+  "  (or\n"
+  "    (< macosMinorVersion 9)\n"
+  "    (< sandbox-level 1))\n"
+  "  (allow default)\n"
+  "  (begin\n"
+  "    (deny default)\n"
+  "    (debug deny)\n"
   "\n"
-  "  (define (home-regex home-relative-regex)\n"
-  "    (resolving-regex (string-append \"^\" (regex-quote home-path) home-relative-regex)))\n"
-  "  (define (home-subpath home-relative-subpath)\n"
-  "    (resolving-subpath (string-append home-path home-relative-subpath)))\n"
-  "  (define (home-literal home-relative-literal)\n"
-  "    (resolving-literal (string-append home-path home-relative-literal)))\n"
+  "    (define resolving-literal literal)\n"
+  "    (define resolving-subpath subpath)\n"
+  "    (define resolving-regex regex)\n"
   "\n"
-  "  (define (container-regex container-relative-regex)\n"
-  "    (resolving-regex (string-append \"^\" (regex-quote container-path) container-relative-regex)))\n"
-  "  (define (container-subpath container-relative-subpath)\n"
-  "    (resolving-subpath (string-append container-path container-relative-subpath)))\n"
-  "  (define (container-literal container-relative-literal)\n"
-  "    (resolving-literal (string-append container-path container-relative-literal)))\n"
+  "    (define container-path appPath)\n"
+  "    (define appdir-path appDir)\n"
+  "    (define var-folders-re \"^/private/var/folders/[^/][^/]\")\n"
+  "    (define var-folders2-re (string-append var-folders-re \"/[^/]+/[^/]\"))\n"
+  "\n"
+  "    (define (home-regex home-relative-regex)\n"
+  "      (resolving-regex (string-append \"^\" (regex-quote home-path) home-relative-regex)))\n"
+  "    (define (home-subpath home-relative-subpath)\n"
+  "      (resolving-subpath (string-append home-path home-relative-subpath)))\n"
+  "    (define (home-literal home-relative-literal)\n"
+  "      (resolving-literal (string-append home-path home-relative-literal)))\n"
   "\n"
-  "  (define (var-folders-regex var-folders-relative-regex)\n"
-  "    (resolving-regex (string-append var-folders-re var-folders-relative-regex)))\n"
-  "  (define (var-folders2-regex var-folders2-relative-regex)\n"
-  "    (resolving-regex (string-append var-folders2-re var-folders2-relative-regex)))\n"
+  "    (define (container-regex container-relative-regex)\n"
+  "      (resolving-regex (string-append \"^\" (regex-quote container-path) container-relative-regex)))\n"
+  "    (define (container-subpath container-relative-subpath)\n"
+  "      (resolving-subpath (string-append container-path container-relative-subpath)))\n"
+  "    (define (container-literal container-relative-literal)\n"
+  "      (resolving-literal (string-append container-path container-relative-literal)))\n"
   "\n"
-  "  (define (appdir-regex appdir-relative-regex)\n"
-  "    (resolving-regex (string-append \"^\" (regex-quote appdir-path) appdir-relative-regex)))\n"
-  "  (define (appdir-subpath appdir-relative-subpath)\n"
-  "    (resolving-subpath (string-append appdir-path appdir-relative-subpath)))\n"
-  "  (define (appdir-literal appdir-relative-literal)\n"
-  "    (resolving-literal (string-append appdir-path appdir-relative-literal)))\n"
+  "    (define (var-folders-regex var-folders-relative-regex)\n"
+  "      (resolving-regex (string-append var-folders-re var-folders-relative-regex)))\n"
+  "    (define (var-folders2-regex var-folders2-relative-regex)\n"
+  "      (resolving-regex (string-append var-folders2-re var-folders2-relative-regex)))\n"
   "\n"
-  "  (define (allow-shared-preferences-read domain)\n"
-  "        (begin\n"
-  "          (if (defined? `user-preference-read)\n"
-  "            (allow user-preference-read (preference-domain domain)))\n"
-  "          (allow file-read*\n"
-  "                 (home-literal (string-append \"/Library/Preferences/\" domain \".plist\"))\n"
-  "                 (home-regex (string-append \"/Library/Preferences/ByHost/\" (regex-quote domain) \"\\..*\\.plist$\")))\n"
-  "          ))\n"
+  "    (define (appdir-regex appdir-relative-regex)\n"
+  "      (resolving-regex (string-append \"^\" (regex-quote appdir-path) appdir-relative-regex)))\n"
+  "    (define (appdir-subpath appdir-relative-subpath)\n"
+  "      (resolving-subpath (string-append appdir-path appdir-relative-subpath)))\n"
+  "    (define (appdir-literal appdir-relative-literal)\n"
+  "      (resolving-literal (string-append appdir-path appdir-relative-literal)))\n"
   "\n"
-  "  (define (allow-shared-list domain)\n"
-  "    (allow file-read*\n"
-  "           (home-regex (string-append \"/Library/Preferences/\" (regex-quote domain)))))\n"
+  "    (define (allow-shared-preferences-read domain)\n"
+  "          (begin\n"
+  "            (if (defined? `user-preference-read)\n"
+  "              (allow user-preference-read (preference-domain domain)))\n"
+  "            (allow file-read*\n"
+  "                   (home-literal (string-append \"/Library/Preferences/\" domain \".plist\"))\n"
+  "                   (home-regex (string-append \"/Library/Preferences/ByHost/\" (regex-quote domain) \"\\..*\\.plist$\")))\n"
+  "            ))\n"
   "\n"
-  "  (allow file-read-metadata)\n"
+  "    (define (allow-shared-list domain)\n"
+  "      (allow file-read*\n"
+  "             (home-regex (string-append \"/Library/Preferences/\" (regex-quote domain)))))\n"
   "\n"
-  "  (allow ipc-posix-shm\n"
-  "      (ipc-posix-name-regex \"^/tmp/com.apple.csseed:\")\n"
-  "      (ipc-posix-name-regex \"^CFPBS:\")\n"
-  "      (ipc-posix-name-regex \"^AudioIO\"))\n"
+  "    (allow file-read-metadata)\n"
+  "\n"
+  "    (allow ipc-posix-shm\n"
+  "        (ipc-posix-name-regex \"^/tmp/com.apple.csseed:\")\n"
+  "        (ipc-posix-name-regex \"^CFPBS:\")\n"
+  "        (ipc-posix-name-regex \"^AudioIO\"))\n"
   "\n"
-  "  (allow file-read-metadata\n"
-  "      (literal \"/home\")\n"
-  "      (literal \"/net\")\n"
-  "      (regex \"^/private/tmp/KSInstallAction\\.\")\n"
-  "      (var-folders-regex \"/\")\n"
-  "      (home-subpath \"/Library\"))\n"
+  "    (allow file-read-metadata\n"
+  "        (literal \"/home\")\n"
+  "        (literal \"/net\")\n"
+  "        (regex \"^/private/tmp/KSInstallAction\\.\")\n"
+  "        (var-folders-regex \"/\")\n"
+  "        (home-subpath \"/Library\"))\n"
   "\n"
-  "  (allow signal (target self))\n"
-  "  (allow job-creation (literal \"/Library/CoreMediaIO/Plug-Ins/DAL\"))\n"
-  "  (allow iokit-set-properties (iokit-property \"IOAudioControlValue\"))\n"
+  "    (allow signal (target self))\n"
+  "    (allow job-creation (literal \"/Library/CoreMediaIO/Plug-Ins/DAL\"))\n"
+  "    (allow iokit-set-properties (iokit-property \"IOAudioControlValue\"))\n"
   "\n"
-  "  (allow mach-lookup\n"
-  "      (global-name \"com.apple.coreservices.launchservicesd\")\n"
-  "      (global-name \"com.apple.coreservices.appleevents\")\n"
-  "      (global-name \"com.apple.pasteboard.1\")\n"
-  "      (global-name \"com.apple.window_proxies\")\n"
-  "      (global-name \"com.apple.windowserver.active\")\n"
-  "      (global-name \"com.apple.audio.coreaudiod\")\n"
-  "      (global-name \"com.apple.audio.audiohald\")\n"
-  "      (global-name \"com.apple.PowerManagement.control\")\n"
-  "      (global-name \"com.apple.cmio.VDCAssistant\")\n"
-  "      (global-name \"com.apple.SystemConfiguration.configd\")\n"
-  "      (global-name \"com.apple.iconservices\")\n"
-  "      (global-name \"com.apple.cookied\")\n"
-  "      (global-name \"com.apple.printuitool.agent\")\n"
-  "      (global-name \"com.apple.printtool.agent\")\n"
-  "      (global-name \"com.apple.cache_delete\")\n"
-  "      (global-name \"com.apple.pluginkit.pkd\")\n"
-  "      (global-name \"com.apple.bird\")\n"
-  "      (global-name \"com.apple.ocspd\")\n"
-  "      (global-name \"com.apple.cmio.AppleCameraAssistant\")\n"
-  "      (global-name \"com.apple.DesktopServicesHelper\")\n"
-  "      (global-name \"com.apple.printtool.daemon\"))\n"
+  "    (allow mach-lookup\n"
+  "        (global-name \"com.apple.coreservices.launchservicesd\")\n"
+  "        (global-name \"com.apple.coreservices.appleevents\")\n"
+  "        (global-name \"com.apple.pasteboard.1\")\n"
+  "        (global-name \"com.apple.window_proxies\")\n"
+  "        (global-name \"com.apple.windowserver.active\")\n"
+  "        (global-name \"com.apple.audio.coreaudiod\")\n"
+  "        (global-name \"com.apple.audio.audiohald\")\n"
+  "        (global-name \"com.apple.PowerManagement.control\")\n"
+  "        (global-name \"com.apple.cmio.VDCAssistant\")\n"
+  "        (global-name \"com.apple.SystemConfiguration.configd\")\n"
+  "        (global-name \"com.apple.iconservices\")\n"
+  "        (global-name \"com.apple.cookied\")\n"
+  "        (global-name \"com.apple.printuitool.agent\")\n"
+  "        (global-name \"com.apple.printtool.agent\")\n"
+  "        (global-name \"com.apple.cache_delete\")\n"
+  "        (global-name \"com.apple.pluginkit.pkd\")\n"
+  "        (global-name \"com.apple.bird\")\n"
+  "        (global-name \"com.apple.ocspd\")\n"
+  "        (global-name \"com.apple.cmio.AppleCameraAssistant\")\n"
+  "        (global-name \"com.apple.DesktopServicesHelper\")\n"
+  "        (global-name \"com.apple.printtool.daemon\"))\n"
   "\n"
-  "  (allow iokit-open\n"
-  "      (iokit-user-client-class \"IOHIDParamUserClient\")\n"
-  "      (iokit-user-client-class \"IOAudioControlUserClient\")\n"
-  "      (iokit-user-client-class \"IOAudioEngineUserClient\")\n"
-  "      (iokit-user-client-class \"IGAccelDevice\")\n"
-  "      (iokit-user-client-class \"nvDevice\")\n"
-  "      (iokit-user-client-class \"nvSharedUserClient\")\n"
-  "      (iokit-user-client-class \"nvFermiGLContext\")\n"
-  "      (iokit-user-client-class \"IGAccelGLContext\")\n"
-  "      (iokit-user-client-class \"IGAccelSharedUserClient\")\n"
-  "      (iokit-user-client-class \"IGAccelVideoContextMain\")\n"
-  "      (iokit-user-client-class \"IGAccelVideoContextMedia\")\n"
-  "      (iokit-user-client-class \"IGAccelVideoContextVEBox\")\n"
-  "      (iokit-user-client-class \"RootDomainUserClient\")\n"
-  "      (iokit-user-client-class \"IOUSBDeviceUserClientV2\")\n"
-  "      (iokit-user-client-class \"IOUSBInterfaceUserClientV2\"))\n"
+  "    (allow iokit-open\n"
+  "        (iokit-user-client-class \"IOHIDParamUserClient\")\n"
+  "        (iokit-user-client-class \"IOAudioControlUserClient\")\n"
+  "        (iokit-user-client-class \"IOAudioEngineUserClient\")\n"
+  "        (iokit-user-client-class \"IGAccelDevice\")\n"
+  "        (iokit-user-client-class \"nvDevice\")\n"
+  "        (iokit-user-client-class \"nvSharedUserClient\")\n"
+  "        (iokit-user-client-class \"nvFermiGLContext\")\n"
+  "        (iokit-user-client-class \"IGAccelGLContext\")\n"
+  "        (iokit-user-client-class \"IGAccelSharedUserClient\")\n"
+  "        (iokit-user-client-class \"IGAccelVideoContextMain\")\n"
+  "        (iokit-user-client-class \"IGAccelVideoContextMedia\")\n"
+  "        (iokit-user-client-class \"IGAccelVideoContextVEBox\")\n"
+  "        (iokit-user-client-class \"RootDomainUserClient\")\n"
+  "        (iokit-user-client-class \"IOUSBDeviceUserClientV2\")\n"
+  "        (iokit-user-client-class \"IOUSBInterfaceUserClientV2\"))\n"
   "\n"
   "; depending on systems, the 1st, 2nd or both rules are necessary\n"
-  "  (allow-shared-preferences-read \"com.apple.HIToolbox\")\n"
-  "  (allow file-read-data (literal \"/Library/Preferences/com.apple.HIToolbox.plist\"))\n"
+  "    (allow-shared-preferences-read \"com.apple.HIToolbox\")\n"
+  "    (allow file-read-data (literal \"/Library/Preferences/com.apple.HIToolbox.plist\"))\n"
   "\n"
-  "  (allow-shared-preferences-read \"com.apple.ATS\")\n"
-  "  (allow file-read-data (literal \"/Library/Preferences/.GlobalPreferences.plist\"))\n"
+  "    (allow-shared-preferences-read \"com.apple.ATS\")\n"
+  "    (allow file-read-data (literal \"/Library/Preferences/.GlobalPreferences.plist\"))\n"
   "\n"
-  "  (allow file-read*\n"
-  "      (subpath \"/Library/Fonts\")\n"
-  "      (subpath \"/Library/Audio/Plug-Ins\")\n"
-  "      (subpath \"/Library/CoreMediaIO/Plug-Ins/DAL\")\n"
-  "      (subpath \"/Library/Spelling\")\n"
-  "      (subpath \"/private/etc/cups/ppd\")\n"
-  "      (subpath \"/private/var/run/cupsd\")\n"
-  "      (literal \"/\")\n"
-  "      (literal \"/private/tmp\")\n"
-  "      (literal \"/private/var/tmp\")\n"
+  "    (allow file-read*\n"
+  "        (subpath \"/Library/Fonts\")\n"
+  "        (subpath \"/Library/Audio/Plug-Ins\")\n"
+  "        (subpath \"/Library/CoreMediaIO/Plug-Ins/DAL\")\n"
+  "        (subpath \"/Library/Spelling\")\n"
+  "        (subpath \"/private/etc/cups/ppd\")\n"
+  "        (subpath \"/private/var/run/cupsd\")\n"
+  "        (literal \"/\")\n"
+  "        (literal \"/private/tmp\")\n"
+  "        (literal \"/private/var/tmp\")\n"
   "\n"
-  "      (home-literal \"/.CFUserTextEncoding\")\n"
-  "      (home-literal \"/Library/Preferences/com.apple.DownloadAssessment.plist\")\n"
-  "      (home-subpath \"/Library/Colors\")\n"
-  "      (home-subpath \"/Library/Fonts\")\n"
-  "      (home-subpath \"/Library/FontCollections\")\n"
-  "      (home-subpath \"/Library/Keyboard Layouts\")\n"
-  "      (home-subpath \"/Library/Input Methods\")\n"
-  "      (home-subpath \"/Library/PDF Services\")\n"
-  "      (home-subpath \"/Library/Spelling\")\n"
+  "        (home-literal \"/.CFUserTextEncoding\")\n"
+  "        (home-literal \"/Library/Preferences/com.apple.DownloadAssessment.plist\")\n"
+  "        (home-subpath \"/Library/Colors\")\n"
+  "        (home-subpath \"/Library/Fonts\")\n"
+  "        (home-subpath \"/Library/FontCollections\")\n"
+  "        (home-subpath \"/Library/Keyboard Layouts\")\n"
+  "        (home-subpath \"/Library/Input Methods\")\n"
+  "        (home-subpath \"/Library/PDF Services\")\n"
+  "        (home-subpath \"/Library/Spelling\")\n"
   "\n"
-  "      (subpath appdir-path)\n"
+  "        (subpath appdir-path)\n"
   "\n"
-  "      (literal appPath)\n"
-  "      (literal appBinaryPath))\n"
+  "        (literal appPath)\n"
+  "        (literal appBinaryPath))\n"
   "\n"
-  "  (allow-shared-list \"org.mozilla.plugincontainer\")\n"
+  "    (allow-shared-list \"org.mozilla.plugincontainer\")\n"
   "\n"
   "; the following 2 rules should be removed when microphone and camera access\n"
   "; are brokered through the content process\n"
-  "  (allow device-microphone)\n"
-  "  (allow device-camera)\n"
+  "    (allow device-microphone)\n"
+  "    (allow device-camera)\n"
   "\n"
-  "  (allow file* (var-folders2-regex \"/com\\.apple\\.IntlDataCache\\.le$\"))\n"
-  "  (allow file-read*\n"
-  "      (var-folders2-regex \"/com\\.apple\\.IconServices/\")\n"
-  "      (var-folders2-regex \"/[^/]+\\.mozrunner/extensions/[^/]+/chrome/[^/]+/content/[^/]+\\.j(s|ar)$\"))\n"
+  "    (allow file* (var-folders2-regex \"/com\\.apple\\.IntlDataCache\\.le$\"))\n"
+  "    (allow file-read*\n"
+  "        (var-folders2-regex \"/com\\.apple\\.IconServices/\")\n"
+  "        (var-folders2-regex \"/[^/]+\\.mozrunner/extensions/[^/]+/chrome/[^/]+/content/[^/]+\\.j(s|ar)$\"))\n"
   "\n"
-  "  (allow file-write* (var-folders2-regex \"/org\\.chromium\\.[a-zA-Z0-9]*$\"))\n"
-  "  (allow file-read*\n"
-  "      (home-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
-  "      (resolving-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
-  "      (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/extensions/\")\n"
-  "      (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/weave/\"))\n"
+  "    (allow file-write* (var-folders2-regex \"/org\\.chromium\\.[a-zA-Z0-9]*$\"))\n"
+  "    (allow file-read*\n"
+  "        (home-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
+  "        (resolving-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
+  "        (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/extensions/\")\n"
+  "        (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/weave/\"))\n"
   "\n"
   "; the following rules should be removed when printing and \n"
   "; opening a file from disk are brokered through the main process\n"
-  "  (if\n"
-  "    (< sandbox-level 2)\n"
-  "    (allow file*\n"
-  "        (require-not\n"
-  "            (home-subpath \"/Library\")))\n"
-  "    (allow file*\n"
-  "        (require-all\n"
-  "            (subpath home-path)\n"
-  "            (require-not\n"
-  "                (home-subpath \"/Library\")))))\n"
+  "    (if\n"
+  "      (< sandbox-level 2)\n"
+  "      (allow file*\n"
+  "          (require-not\n"
+  "              (home-subpath \"/Library\")))\n"
+  "      (allow file*\n"
+  "          (require-all\n"
+  "              (subpath home-path)\n"
+  "              (require-not\n"
+  "                  (home-subpath \"/Library\")))))\n"
   "\n"
   "; printing\n"
-  "  (allow authorization-right-obtain\n"
-  "         (right-name \"system.print.operator\")\n"
-  "         (right-name \"system.printingmanager\"))\n"
-  "  (allow mach-lookup\n"
-  "         (global-name \"com.apple.printuitool.agent\")\n"
-  "         (global-name \"com.apple.printtool.agent\")\n"
-  "         (global-name \"com.apple.printtool.daemon\")\n"
-  "         (global-name \"com.apple.sharingd\")\n"
-  "         (global-name \"com.apple.metadata.mds\")\n"
-  "         (global-name \"com.apple.mtmd.xpc\")\n"
-  "         (global-name \"com.apple.FSEvents\")\n"
-  "         (global-name \"com.apple.locum\")\n"
-  "         (global-name \"com.apple.ImageCaptureExtension2.presence\"))\n"
-  "  (allow file-read*\n"
-  "         (home-literal \"/.cups/lpoptions\")\n"
-  "         (home-literal \"/.cups/client.conf\")\n"
-  "         (literal \"/private/etc/cups/lpoptions\")\n"
-  "         (literal \"/private/etc/cups/client.conf\")\n"
-  "         (subpath \"/private/etc/cups/ppd\")\n"
-  "         (literal \"/private/var/run/cupsd\"))\n"
-  "  (allow-shared-preferences-read \"org.cups.PrintingPrefs\")\n"
-  "  (allow-shared-preferences-read \"com.apple.finder\")\n"
-  "  (allow-shared-preferences-read \"com.apple.LaunchServices\")\n"
-  "  (allow-shared-preferences-read \".GlobalPreferences\")\n"
-  "  (allow network-outbound\n"
-  "      (literal \"/private/var/run/cupsd\")\n"
-  "      (literal \"/private/var/run/mDNSResponder\"))\n"
+  "    (allow authorization-right-obtain\n"
+  "           (right-name \"system.print.operator\")\n"
+  "           (right-name \"system.printingmanager\"))\n"
+  "    (allow mach-lookup\n"
+  "           (global-name \"com.apple.printuitool.agent\")\n"
+  "           (global-name \"com.apple.printtool.agent\")\n"
+  "           (global-name \"com.apple.printtool.daemon\")\n"
+  "           (global-name \"com.apple.sharingd\")\n"
+  "           (global-name \"com.apple.metadata.mds\")\n"
+  "           (global-name \"com.apple.mtmd.xpc\")\n"
+  "           (global-name \"com.apple.FSEvents\")\n"
+  "           (global-name \"com.apple.locum\")\n"
+  "           (global-name \"com.apple.ImageCaptureExtension2.presence\"))\n"
+  "    (allow file-read*\n"
+  "           (home-literal \"/.cups/lpoptions\")\n"
+  "           (home-literal \"/.cups/client.conf\")\n"
+  "           (literal \"/private/etc/cups/lpoptions\")\n"
+  "           (literal \"/private/etc/cups/client.conf\")\n"
+  "           (subpath \"/private/etc/cups/ppd\")\n"
+  "           (literal \"/private/var/run/cupsd\"))\n"
+  "    (allow-shared-preferences-read \"org.cups.PrintingPrefs\")\n"
+  "    (allow-shared-preferences-read \"com.apple.finder\")\n"
+  "    (allow-shared-preferences-read \"com.apple.LaunchServices\")\n"
+  "    (allow-shared-preferences-read \".GlobalPreferences\")\n"
+  "    (allow network-outbound\n"
+  "        (literal \"/private/var/run/cupsd\")\n"
+  "        (literal \"/private/var/run/mDNSResponder\"))\n"
   "\n"
   "; print preview\n"
-  "  (allow lsopen)\n"
-  "  (allow file-write* file-issue-extension (var-folders2-regex \"/\"))\n"
-  "  (allow file-read-xattr (literal \"/Applications/Preview.app\"))\n"
-  "  (allow mach-task-name)\n"
-  "  (allow mach-register)\n"
-  "  (allow file-read-data\n"
-  "      (regex \"^/Library/Printers/[^/]+/PDEs/[^/]+.plugin\")\n"
-  "      (subpath \"/Library/PDF Services\")\n"
-  "      (subpath \"/Applications/Preview.app\")\n"
-  "      (home-literal \"/Library/Preferences/com.apple.ServicesMenu.Services.plist\"))\n"
-  "  (allow mach-lookup\n"
-  "      (global-name \"com.apple.pbs.fetch_services\")\n"
-  "      (global-name \"com.apple.tsm.uiserver\")\n"
-  "      (global-name \"com.apple.ls.boxd\")\n"
-  "      (global-name \"com.apple.coreservices.quarantine-resolver\")\n"
-  "      (global-name-regex \"_OpenStep$\"))\n"
-  "  (allow appleevent-send\n"
-  "      (appleevent-destination \"com.apple.preview\")\n"
-  "      (appleevent-destination \"com.apple.imagecaptureextension2\"))\n"
+  "    (if (> macosMinorVersion 9)\n"
+  "        (allow lsopen))\n"
+  "    (allow file-write* file-issue-extension (var-folders2-regex \"/\"))\n"
+  "    (allow file-read-xattr (literal \"/Applications/Preview.app\"))\n"
+  "    (allow mach-task-name)\n"
+  "    (allow mach-register)\n"
+  "    (allow file-read-data\n"
+  "        (regex \"^/Library/Printers/[^/]+/PDEs/[^/]+.plugin\")\n"
+  "        (subpath \"/Library/PDF Services\")\n"
+  "        (subpath \"/Applications/Preview.app\")\n"
+  "        (home-literal \"/Library/Preferences/com.apple.ServicesMenu.Services.plist\"))\n"
+  "    (allow mach-lookup\n"
+  "        (global-name \"com.apple.pbs.fetch_services\")\n"
+  "        (global-name \"com.apple.tsm.uiserver\")\n"
+  "        (global-name \"com.apple.ls.boxd\")\n"
+  "        (global-name \"com.apple.coreservices.quarantine-resolver\")\n"
+  "        (global-name-regex \"_OpenStep$\"))\n"
+  "    (allow appleevent-send\n"
+  "        (appleevent-destination \"com.apple.preview\")\n"
+  "        (appleevent-destination \"com.apple.imagecaptureextension2\"))\n"
   "\n"
   "; accelerated graphics\n"
-  "  (allow-shared-preferences-read \"com.apple.opengl\")\n"
-  "  (allow-shared-preferences-read \"com.nvidia.OpenGL\")\n"
-  "  (allow mach-lookup\n"
-  "      (global-name \"com.apple.cvmsServ\"))\n"
-  "  (allow iokit-open\n"
-  "      (iokit-connection \"IOAccelerator\")\n"
-  "      (iokit-user-client-class \"IOAccelerationUserClient\")\n"
-  "      (iokit-user-client-class \"IOSurfaceRootUserClient\")\n"
-  "      (iokit-user-client-class \"IOSurfaceSendRight\")\n"
-  "      (iokit-user-client-class \"IOFramebufferSharedUserClient\")\n"
-  "      (iokit-user-client-class \"AppleSNBFBUserClient\")\n"
-  "      (iokit-user-client-class \"AGPMClient\")\n"
-  "      (iokit-user-client-class \"AppleGraphicsControlClient\")\n"
-  "      (iokit-user-client-class \"AppleGraphicsPolicyClient\"))\n"
+  "    (allow-shared-preferences-read \"com.apple.opengl\")\n"
+  "    (allow-shared-preferences-read \"com.nvidia.OpenGL\")\n"
+  "    (allow mach-lookup\n"
+  "        (global-name \"com.apple.cvmsServ\"))\n"
+  "    (allow iokit-open\n"
+  "        (iokit-connection \"IOAccelerator\")\n"
+  "        (iokit-user-client-class \"IOAccelerationUserClient\")\n"
+  "        (iokit-user-client-class \"IOSurfaceRootUserClient\")\n"
+  "        (iokit-user-client-class \"IOSurfaceSendRight\")\n"
+  "        (iokit-user-client-class \"IOFramebufferSharedUserClient\")\n"
+  "        (iokit-user-client-class \"AppleSNBFBUserClient\")\n"
+  "        (iokit-user-client-class \"AGPMClient\")\n"
+  "        (iokit-user-client-class \"AppleGraphicsControlClient\")\n"
+  "        (iokit-user-client-class \"AppleGraphicsPolicyClient\"))\n"
   "\n"
   "; bug 1153809\n"
-  "  (allow iokit-open\n"
-  "      (iokit-user-client-class \"NVDVDContextTesla\")\n"
-  "      (iokit-user-client-class \"Gen6DVDContext\"))\n"
+  "    (allow iokit-open\n"
+  "        (iokit-user-client-class \"NVDVDContextTesla\")\n"
+  "        (iokit-user-client-class \"Gen6DVDContext\"))\n"
   "\n"
   "; bug 1190032\n"
-  "  (allow file*\n"
-  "      (home-regex \"/Library/Caches/TemporaryItems/plugtmp.*\"))\n"
+  "    (allow file*\n"
+  "        (home-regex \"/Library/Caches/TemporaryItems/plugtmp.*\"))\n"
   "\n"
   "; bug 1201935\n"
-  "  (allow file-read*\n"
-  "      (home-subpath \"/Library/Caches/TemporaryItems\"))\n"
+  "    (allow file-read*\n"
+  "        (home-subpath \"/Library/Caches/TemporaryItems\"))\n"
   "\n"
   "; bug 1237847\n"
-  "  (allow file-read*\n"
-  "      (subpath appTempDir))\n"
-  "  (allow file-write*\n"
-  "      (subpath appTempDir))\n"
+  "    (allow file-read*\n"
+  "        (subpath appTempDir))\n"
+  "    (allow file-write*\n"
+  "        (subpath appTempDir))\n"
+  "  )\n"
   ")\n";
 
 bool StartMacSandbox(MacSandboxInfo aInfo, std::string &aErrorMessage)
 {
   char *profile = NULL;
   if (aInfo.type == MacSandboxType_Plugin) {
-    asprintf(&profile, pluginSandboxRules,
-             aInfo.pluginInfo.pluginBinaryPath.c_str(),
-             aInfo.appPath.c_str(),
-             aInfo.appBinaryPath.c_str());
+    if (OSXVersion::OnLionOrLater()) {
+      asprintf(&profile, pluginSandboxRules, "", ";",
+               aInfo.pluginInfo.pluginPath.c_str(),
+               aInfo.pluginInfo.pluginBinaryPath.c_str(),
+               aInfo.appPath.c_str(),
+               aInfo.appBinaryPath.c_str());
+    } else {
+      asprintf(&profile, pluginSandboxRules, ";", "",
+               aInfo.pluginInfo.pluginPath.c_str(),
+               aInfo.pluginInfo.pluginBinaryPath.c_str(),
+               aInfo.appPath.c_str(),
+               aInfo.appBinaryPath.c_str());
+    }
 
     if (profile &&
       aInfo.pluginInfo.type == MacSandboxPluginType_GMPlugin_EME_Widevine) {
       char *widevineProfile = NULL;
       asprintf(&widevineProfile, "%s%s", profile,
         widevinePluginSandboxRulesAddend);
       free(profile);
       profile = widevineProfile;
     }
   }
   else if (aInfo.type == MacSandboxType_Content) {
-    if (aInfo.level >= 1) {
-      asprintf(&profile, contentSandboxRules, aInfo.level,
-               aInfo.appPath.c_str(),
-               aInfo.appBinaryPath.c_str(),
-               aInfo.appDir.c_str(),
-               aInfo.appTempDir.c_str(),
-               getenv("HOME"));
-    } else {
-      fprintf(stderr,
-        "Content sandbox disabled due to sandbox level setting\n");
-      return (true);
-    }
+    asprintf(&profile, contentSandboxRules, aInfo.level,
+             OSXVersion::OSXVersionMinor(),
+             aInfo.appPath.c_str(),
+             aInfo.appBinaryPath.c_str(),
+             aInfo.appDir.c_str(),
+             aInfo.appTempDir.c_str(),
+             getenv("HOME"));
   }
   else {
     char *msg = NULL;
     asprintf(&msg, "Unexpected sandbox type %u", aInfo.type);
     if (msg) {
       aErrorMessage.assign(msg);
       free(msg);
     }
--- 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);