Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 13 Oct 2016 11:58:40 +0200
changeset 317858 6359c12a6399b356da32d77cb2416a387c475844
parent 317857 d1ed33f3fdd269bbe543d822b5a0cbd390ba0c68 (current diff)
parent 317781 f03e2740d604d339ed553dad62a3fc54c317f8fa (diff)
child 317859 3e30051824704f61374030a645c5be6219d2e201
push id33170
push usercbook@mozilla.com
push dateFri, 14 Oct 2016 10:37:07 +0000
treeherderautoland@0d101ebfd95c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone52.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 mozilla-central to mozilla-inbound
netwerk/protocol/http/HttpBaseChannel.cpp
security/manager/pki/resources/content/editcacert.js
security/manager/ssl/tests/mochitest/browser/browser_editCACertTrust.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1415,19 +1415,21 @@ pref("privacy.trackingprotection.ui.enab
 #endif
 pref("privacy.trackingprotection.introCount", 0);
 pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
 
 // Enable Contextual Identity Containers
 #ifdef NIGHTLY_BUILD
 pref("privacy.userContext.enabled", true);
 pref("privacy.userContext.ui.enabled", true);
+pref("privacy.usercontext.about_newtab_segregation.enabled", true);
 #else
 pref("privacy.userContext.enabled", false);
 pref("privacy.userContext.ui.enabled", false);
+pref("privacy.usercontext.about_newtab_segregation.enabled", false);
 #endif
 
 #ifndef RELEASE_OR_BETA
 // At the moment, autostart.2 is used, while autostart.1 is unused.
 // We leave it here set to false to reset users' defaults and allow
 // us to change everybody to true in the future, when desired.
 pref("browser.tabs.remote.autostart.1", false);
 pref("browser.tabs.remote.autostart.2", true);
--- a/browser/components/contextualidentity/test/browser/browser_aboutURLs.js
+++ b/browser/components/contextualidentity/test/browser/browser_aboutURLs.js
@@ -33,16 +33,17 @@ add_task(function* () {
       }
     } catch (e) {
       // getService might have thrown if the component doesn't actually
       // implement nsIAboutModule
     }
   }
 
   for (let url of aboutURLs) {
+    info("Loading about:" + url);
     let tab = gBrowser.addTab("about:"+url, {userContextId: 1});
     yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
-    ok(true);
+    ok(true, "Done loading about:" + url);
 
     yield BrowserTestUtils.removeTab(tab);
   }
 });
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -123,16 +123,18 @@ skip-if = os == "mac" # Bug 1245996 : cl
 [browser_rules_edit-selector_02.js]
 [browser_rules_edit-selector_03.js]
 [browser_rules_edit-selector_04.js]
 [browser_rules_edit-selector_05.js]
 [browser_rules_edit-selector_06.js]
 [browser_rules_edit-selector_07.js]
 [browser_rules_edit-selector_08.js]
 [browser_rules_edit-selector_09.js]
+[browser_rules_edit-selector_10.js]
+[browser_rules_edit-selector_11.js]
 [browser_rules_edit-value-after-name_01.js]
 [browser_rules_edit-value-after-name_02.js]
 [browser_rules_edit-value-after-name_03.js]
 [browser_rules_edit-value-after-name_04.js]
 [browser_rules_editable-field-focus_01.js]
 [browser_rules_editable-field-focus_02.js]
 [browser_rules_eyedropper.js]
 [browser_rules_filtereditor-appears-on-swatch-click.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_10.js
@@ -0,0 +1,64 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Regression test for bug 1293616: make sure that editing a selector
+// keeps the rule in the proper position.
+
+const TEST_URI = `
+  <style type="text/css">
+    #testid span, #testid p {
+      background: aqua;
+    }
+    span {
+      background: fuchsia;
+    }
+  </style>
+  <div id="testid">
+    <span class="pickme">
+      Styled Node
+    </span>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  yield selectNode(".pickme", inspector);
+  yield testEditSelector(view);
+});
+
+function* testEditSelector(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+  editor.input.value = "#testid span";
+  let onRuleViewChanged = once(view, "ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  yield onRuleViewChanged;
+
+  // Escape the new property editor after editing the selector
+  let onBlur = once(view.styleDocument.activeElement, "blur");
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+  yield onBlur;
+
+  // Get the new rule editor that replaced the original
+  ruleEditor = getRuleViewRuleEditor(view, 1);
+
+  info("Check that the correct rules are visible");
+  is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
+  is(ruleEditor.element.getAttribute("unmatched"), "false", "Rule editor is matched.");
+
+  let props = ruleEditor.rule.textProps;
+  is(props.length, 1, "Rule has correct number of properties");
+  is(props[0].name, "background", "Found background property");
+  ok(!props[0].overridden, "Background property is not overridden");
+
+  ruleEditor = getRuleViewRuleEditor(view, 2);
+  props = ruleEditor.rule.textProps;
+  is(props.length, 1, "Rule has correct number of properties");
+  is(props[0].name, "background", "Found background property");
+  ok(props[0].overridden, "Background property is overridden");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_11.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Regression test for bug 1293616, where editing a selector should
+// change the relative priority of the rule.
+
+const TEST_URI = `
+  <style type="text/css">
+    #testid {
+      background: aqua;
+    }
+    .pickme {
+      background: seagreen;
+    }
+    span {
+      background: fuchsia;
+    }
+  </style>
+  <div>
+    <span id="testid" class="pickme">
+      Styled Node
+    </span>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  yield selectNode(".pickme", inspector);
+  yield testEditSelector(view);
+});
+
+function* testEditSelector(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = yield focusEditableField(view, ruleEditor.selectorText);
+
+  editor.input.value = ".pickme";
+  let onRuleViewChanged = once(view, "ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  yield onRuleViewChanged;
+
+  // Escape the new property editor after editing the selector
+  let onBlur = once(view.styleDocument.activeElement, "blur");
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
+  yield onBlur;
+
+  // Get the new rule editor that replaced the original
+  ruleEditor = getRuleViewRuleEditor(view, 1);
+
+  info("Check that the correct rules are visible");
+  is(view._elementStyle.rules.length, 4, "Should have 4 rules.");
+  is(ruleEditor.element.getAttribute("unmatched"), "false", "Rule editor is matched.");
+
+  let props = ruleEditor.rule.textProps;
+  is(props.length, 1, "Rule has correct number of properties");
+  is(props[0].name, "background", "Found background property");
+  is(props[0].value, "aqua", "Background property is aqua");
+  ok(props[0].overridden, "Background property is overridden");
+
+  ruleEditor = getRuleViewRuleEditor(view, 2);
+  props = ruleEditor.rule.textProps;
+  is(props.length, 1, "Rule has correct number of properties");
+  is(props[0].name, "background", "Found background property");
+  is(props[0].value, "seagreen", "Background property is seagreen");
+  ok(!props[0].overridden, "Background property is not overridden");
+}
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -22,16 +22,17 @@ const {
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
 } = require("devtools/shared/css/parsing-utils");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
+const {Task} = require("devtools/shared/task");
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools-shared/locale/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
@@ -512,72 +513,98 @@ RuleEditor.prototype = {
    *
    * @param {String} value
    *        The value contained in the editor.
    * @param {Boolean} commit
    *        True if the change should be applied.
    * @param {Number} direction
    *        The move focus direction number.
    */
-  _onSelectorDone: function (value, commit, direction) {
+  _onSelectorDone: Task.async(function* (value, commit, direction) {
     if (!commit || this.isEditing || value === "" ||
         value === this.rule.selectorText) {
       return;
     }
 
     let ruleView = this.ruleView;
     let elementStyle = ruleView._elementStyle;
     let element = elementStyle.element;
     let supportsUnmatchedRules =
       this.rule.domRule.supportsModifySelectorUnmatched;
 
     this.isEditing = true;
 
-    this.rule.domRule.modifySelector(element, value).then(response => {
-      this.isEditing = false;
+    try {
+      let response = yield this.rule.domRule.modifySelector(element, value);
 
       if (!supportsUnmatchedRules) {
+        this.isEditing = false;
+
         if (response) {
           this.ruleView.refreshPanel();
         }
         return;
       }
 
+      // We recompute the list of applied styles, because editing a
+      // selector might cause this rule's position to change.
+      let applied = yield elementStyle.pageStyle.getApplied(element, {
+        inherited: true,
+        matchedSelectors: true,
+        filter: elementStyle.showUserAgentStyles ? "ua" : undefined
+      });
+
+      this.isEditing = false;
+
       let {ruleProps, isMatching} = response;
       if (!ruleProps) {
         // Notify for changes, even when nothing changes,
         // just to allow tests being able to track end of this request.
         ruleView.emit("ruleview-invalid-selector");
         return;
       }
 
       ruleProps.isUnmatched = !isMatching;
       let newRule = new Rule(elementStyle, ruleProps);
       let editor = new RuleEditor(ruleView, newRule);
       let rules = elementStyle.rules;
 
-      rules.splice(rules.indexOf(this.rule), 1);
-      rules.push(newRule);
+      let newRuleIndex = applied.findIndex((r) => r.rule == ruleProps.rule);
+      let oldIndex = rules.indexOf(this.rule);
+
+      // If the selector no longer matches, then we leave the rule in
+      // the same relative position.
+      if (newRuleIndex === -1) {
+        newRuleIndex = oldIndex;
+      }
+
+      // Remove the old rule and insert the new rule.
+      rules.splice(oldIndex, 1);
+      rules.splice(newRuleIndex, 0, newRule);
       elementStyle._changed();
       elementStyle.markOverriddenAll();
 
+      // We install the new editor in place of the old -- you might
+      // think we would replicate the list-modification logic above,
+      // but that is complicated due to the way the UI installs
+      // pseudo-element rules and the like.
       this.element.parentNode.replaceChild(editor.element, this.element);
 
       // Remove highlight for modified selector
       if (ruleView.highlightedSelector) {
         ruleView.toggleSelectorHighlighter(ruleView.lastSelectorIcon,
           ruleView.highlightedSelector);
       }
 
       editor._moveSelectorFocus(direction);
-    }).then(null, err => {
+    } catch (err) {
       this.isEditing = false;
       promiseWarn(err);
-    });
-  },
+    }
+  }),
 
   /**
    * Handle moving the focus change after a tab or return keypress in the
    * selector inplace editor.
    *
    * @param {Number} direction
    *        The move focus direction number.
    */
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/events.js
@@ -0,0 +1,86 @@
+"use strict";
+
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+  // When the monitored target begins and finishes navigating.
+  TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
+  TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
+
+  // When a network or timeline event is received.
+  // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
+  // more information about what each packet is supposed to deliver.
+  NETWORK_EVENT: "NetMonitor:NetworkEvent",
+  TIMELINE_EVENT: "NetMonitor:TimelineEvent",
+
+  // When a network event is added to the view
+  REQUEST_ADDED: "NetMonitor:RequestAdded",
+
+  // When request headers begin and finish receiving.
+  UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders",
+  RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders",
+
+  // When request cookies begin and finish receiving.
+  UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
+  RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
+
+  // When request post data begins and finishes receiving.
+  UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData",
+  RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData",
+
+  // When security information begins and finishes receiving.
+  UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo",
+  RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo",
+
+  // When response headers begin and finish receiving.
+  UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders",
+  RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders",
+
+  // When response cookies begin and finish receiving.
+  UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies",
+  RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies",
+
+  // When event timings begin and finish receiving.
+  UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
+  RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
+
+  // When response content begins, updates and finishes receiving.
+  STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
+  UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
+  RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
+
+  // When the request post params are displayed in the UI.
+  REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
+
+  // When the response body is displayed in the UI.
+  RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
+
+  // When the html response preview is displayed in the UI.
+  RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable",
+
+  // When the image response thumbnail is displayed in the UI.
+  RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
+    "NetMonitor:ResponseImageThumbnailAvailable",
+
+  // When a tab is selected in the NetworkDetailsView and subsequently rendered.
+  TAB_UPDATED: "NetMonitor:TabUpdated",
+
+  // Fired when Sidebar has finished being populated.
+  SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
+
+  // Fired when NetworkDetailsView has finished being populated.
+  NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
+
+  // Fired when CustomRequestView has finished being populated.
+  CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
+
+  // Fired when charts have been displayed in the PerformanceStatisticsView.
+  PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
+  PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
+  EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
+
+  // Fired once the NetMonitorController establishes a connection to the debug
+  // target.
+  CONNECTED: "connected",
+};
+
+exports.EVENTS = EVENTS;
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -3,17 +3,19 @@
 # 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/.
 
 DIRS += [
     'har'
 ]
 
 DevToolsModules(
+    'events.js',
     'filter-predicates.js',
     'l10n.js',
     'panel.js',
     'prefs.js',
     'request-utils.js',
     'requests-menu-view.js',
+    'sort-predicates.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -4,99 +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/. */
 /* globals window, document, NetMonitorView */
 /* exported loader */
 "use strict";
 
 var { utils: Cu } = Components;
 
-// The panel's window global is an EventEmitter firing the following events:
-const EVENTS = {
-  // When the monitored target begins and finishes navigating.
-  TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
-  TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
-
-  // When a network or timeline event is received.
-  // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
-  // more information about what each packet is supposed to deliver.
-  NETWORK_EVENT: "NetMonitor:NetworkEvent",
-  TIMELINE_EVENT: "NetMonitor:TimelineEvent",
-
-  // When a network event is added to the view
-  REQUEST_ADDED: "NetMonitor:RequestAdded",
-
-  // When request headers begin and finish receiving.
-  UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders",
-  RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders",
-
-  // When request cookies begin and finish receiving.
-  UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
-  RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
-
-  // When request post data begins and finishes receiving.
-  UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData",
-  RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData",
-
-  // When security information begins and finishes receiving.
-  UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo",
-  RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo",
-
-  // When response headers begin and finish receiving.
-  UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders",
-  RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders",
-
-  // When response cookies begin and finish receiving.
-  UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies",
-  RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies",
-
-  // When event timings begin and finish receiving.
-  UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
-  RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
-
-  // When response content begins, updates and finishes receiving.
-  STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
-  UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
-  RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
-
-  // When the request post params are displayed in the UI.
-  REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
-
-  // When the response body is displayed in the UI.
-  RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
-
-  // When the html response preview is displayed in the UI.
-  RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable",
-
-  // When the image response thumbnail is displayed in the UI.
-  RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
-    "NetMonitor:ResponseImageThumbnailAvailable",
-
-  // When a tab is selected in the NetworkDetailsView and subsequently rendered.
-  TAB_UPDATED: "NetMonitor:TabUpdated",
-
-  // Fired when Sidebar has finished being populated.
-  SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
-
-  // Fired when NetworkDetailsView has finished being populated.
-  NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
-
-  // Fired when CustomRequestView has finished being populated.
-  CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
-
-  // Fired when charts have been displayed in the PerformanceStatisticsView.
-  PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
-  PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
-  EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
-
-  // Fired once the NetMonitorController establishes a connection to the debug
-  // target.
-  CONNECTED: "connected",
-};
-
 // Descriptions for what this frontend is currently doing.
 const ACTIVITY_TYPE = {
   // Standing by and handling requests normally.
   NONE: 0,
 
   // Forcing the target to reload with cache enabled or disabled.
   RELOAD: {
     WITH_CACHE_ENABLED: 1,
@@ -119,16 +36,17 @@ var { loader, require } = BrowserLoaderM
 const promise = require("promise");
 const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const EventEmitter = require("devtools/shared/event-emitter");
 const Editor = require("devtools/client/sourceeditor/editor");
 const {TimelineFront} = require("devtools/shared/fronts/timeline");
 const {Task} = require("devtools/shared/task");
 const {Prefs} = require("./prefs");
+const {EVENTS} = require("./events");
 
 XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
 XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
 XPCOMUtils.defineConstant(this, "Editor", Editor);
 XPCOMUtils.defineConstant(this, "Prefs", Prefs);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chart",
   "resource://devtools/client/shared/widgets/Chart.jsm");
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -14,17 +14,21 @@ XPCOMUtils.defineLazyGetter(this, "Netwo
 
 const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 const {ToolSidebar} = require("devtools/client/framework/sidebar");
 const { testing: isTesting } = require("devtools/shared/flags");
 const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters} = require("./filter-predicates");
-const {getFormDataSections, formDataURI, writeHeaderText, getKeyWithEvent} = require("./request-utils");
+const {getFormDataSections,
+       formDataURI,
+       writeHeaderText,
+       getKeyWithEvent,
+       getUriHostPort} = require("./request-utils");
 const {L10N} = require("./l10n");
 const {RequestsMenuView} = require("./requests-menu-view");
 
 // ms
 const WDA_DEFAULT_VERIFY_INTERVAL = 50;
 
 // Use longer timeout during testing as the tests need this process to succeed
 // and two seconds is quite short on slow debug builds. The timeout here should
@@ -1310,17 +1314,17 @@ NetworkDetailsView.prototype = {
       let disabledLabel = L10N.getStr("netmonitor.security.disabled");
 
       // Connection parameters
       setValue("#security-protocol-version-value",
         securityInfo.protocolVersion);
       setValue("#security-ciphersuite-value", securityInfo.cipherSuite);
 
       // Host header
-      let domain = NetMonitorView.RequestsMenu._getUriHostPort(url);
+      let domain = getUriHostPort(url);
       let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader",
         domain);
       setValue("#security-info-host-header", hostHeader);
 
       // Parameters related to the domain
       setValue("#security-http-strict-transport-security-value",
                 securityInfo.hsts ? enabledLabel : disabledLabel);
 
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -1,13 +1,14 @@
 "use strict";
 
 const { Ci } = require("chrome");
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 const { Task } = require("devtools/shared/task");
+const NetworkHelper = require("devtools/shared/webconsole/network-helper");
 
 /**
  * Helper method to get a wrapped function which can be bound to as
  * an event listener directly and is executed only when data-key is
  * present in event.target.
  *
  * @param function callback
  *          Function to execute execute when data-key
@@ -104,16 +105,58 @@ exports.formDataURI = function (mimeType
  * @return string text
  *         List of headers in text format
  */
 exports.writeHeaderText = function (headers) {
   return headers.map(({name, value}) => name + ": " + value).join("\n");
 };
 
 /**
+ * Helper for getting an abbreviated string for a mime type.
+ *
+ * @param string mimeType
+ * @return string
+ */
+exports.getAbbreviatedMimeType = function (mimeType) {
+  if (!mimeType) {
+    return "";
+  }
+  return (mimeType.split(";")[0].split("/")[1] || "").split("+")[0];
+};
+
+/**
+ * Helpers for getting details about an nsIURL.
+ *
+ * @param nsIURL | string url
+ * @return string
+ */
+exports.getUriNameWithQuery = function (url) {
+  if (!(url instanceof Ci.nsIURL)) {
+    url = NetworkHelper.nsIURL(url);
+  }
+
+  let name = NetworkHelper.convertToUnicode(
+    unescape(url.fileName || url.filePath || "/"));
+  let query = NetworkHelper.convertToUnicode(unescape(url.query));
+
+  return name + (query ? "?" + query : "");
+};
+
+exports.getUriHostPort = function (url) {
+  if (!(url instanceof Ci.nsIURL)) {
+    url = NetworkHelper.nsIURL(url);
+  }
+  return NetworkHelper.convertToUnicode(unescape(url.hostPort));
+};
+
+exports.getUriHost = function (url) {
+  return exports.getUriHostPort(url).replace(/:\d+$/, "");
+};
+
+/**
  * Convert a nsIContentPolicy constant to a display string
  */
 const LOAD_CAUSE_STRINGS = {
   [Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
   [Ci.nsIContentPolicy.TYPE_OTHER]: "other",
   [Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
   [Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
   [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -11,19 +11,27 @@ const {HTMLTooltip} = require("devtools/
 const {setImageTooltip, getImageDimensions} =
   require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const {Heritage, WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {Curl, CurlUtils} = require("devtools/client/shared/curl");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters, isFreetextMatch} = require("./filter-predicates");
-const {getFormDataSections, formDataURI, writeHeaderText, getKeyWithEvent,
+const {Sorters} = require("./sort-predicates");
+const {L10N, WEBCONSOLE_L10N} = require("./l10n");
+const {getFormDataSections,
+       formDataURI,
+       writeHeaderText,
+       getKeyWithEvent,
+       getAbbreviatedMimeType,
+       getUriNameWithQuery,
+       getUriHostPort,
+       getUriHost,
        loadCauseString} = require("./request-utils");
-const {L10N, WEBCONSOLE_L10N} = require("./l10n");
 
 loader.lazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
 
 loader.lazyRequireGetter(this, "HarExporter",
   "devtools/client/netmonitor/har/har-exporter", true);
 
 loader.lazyRequireGetter(this, "NetworkHelper",
@@ -87,19 +95,16 @@ function RequestsMenuView() {
   dumpn("RequestsMenuView was instantiated");
 
   this._flushRequests = this._flushRequests.bind(this);
   this._onHover = this._onHover.bind(this);
   this._onSelect = this._onSelect.bind(this);
   this._onSwap = this._onSwap.bind(this);
   this._onResize = this._onResize.bind(this);
   this._onScroll = this._onScroll.bind(this);
-  this._byFile = this._byFile.bind(this);
-  this._byDomain = this._byDomain.bind(this);
-  this._byType = this._byType.bind(this);
   this._onSecurityIconClick = this._onSecurityIconClick.bind(this);
 }
 
 RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function () {
@@ -117,17 +122,17 @@ RequestsMenuView.prototype = Heritage.ex
     this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
     this.tooltip.startTogglingOnHover(widgetParentEl, this._onHover, {
       toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
       interactive: true
     });
     $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
 
     Prefs.filters.forEach(type => this.filterOn(type));
-    this.sortContents(this._byTiming);
+    this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
 
     this.allowFocusOnRightClick = true;
     this.maintainSelectionVisible = true;
 
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("swap", this._onSwap, false);
     this._splitter.addEventListener("mousemove", this._onResize, false);
     window.addEventListener("resize", this._onResize, false);
@@ -784,75 +789,75 @@ RequestsMenuView.prototype = Heritage.ex
       // Used to style the next column.
       target.parentNode.setAttribute("active", "true");
     }
 
     // Sort by whatever was requested.
     switch (type) {
       case "status":
         if (direction == "ascending") {
-          this.sortContents(this._byStatus);
+          this.sortContents((a, b) => Sorters.status(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byStatus(a, b));
+          this.sortContents((a, b) => -Sorters.status(a.attachment, b.attachment));
         }
         break;
       case "method":
         if (direction == "ascending") {
-          this.sortContents(this._byMethod);
+          this.sortContents((a, b) => Sorters.method(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byMethod(a, b));
+          this.sortContents((a, b) => -Sorters.method(a.attachment, b.attachment));
         }
         break;
       case "file":
         if (direction == "ascending") {
-          this.sortContents(this._byFile);
+          this.sortContents((a, b) => Sorters.file(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byFile(a, b));
+          this.sortContents((a, b) => -Sorters.file(a.attachment, b.attachment));
         }
         break;
       case "domain":
         if (direction == "ascending") {
-          this.sortContents(this._byDomain);
+          this.sortContents((a, b) => Sorters.domain(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byDomain(a, b));
+          this.sortContents((a, b) => -Sorters.domain(a.attachment, b.attachment));
         }
         break;
       case "cause":
         if (direction == "ascending") {
-          this.sortContents(this._byCause);
+          this.sortContents((a, b) => Sorters.cause(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byCause(a, b));
+          this.sortContents((a, b) => -Sorters.cause(a.attachment, b.attachment));
         }
         break;
       case "type":
         if (direction == "ascending") {
-          this.sortContents(this._byType);
+          this.sortContents((a, b) => Sorters.type(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byType(a, b));
+          this.sortContents((a, b) => -Sorters.type(a.attachment, b.attachment));
         }
         break;
       case "transferred":
         if (direction == "ascending") {
-          this.sortContents(this._byTransferred);
+          this.sortContents((a, b) => Sorters.transferred(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byTransferred(a, b));
+          this.sortContents((a, b) => -Sorters.transferred(a.attachment, b.attachment));
         }
         break;
       case "size":
         if (direction == "ascending") {
-          this.sortContents(this._bySize);
+          this.sortContents((a, b) => Sorters.size(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._bySize(a, b));
+          this.sortContents((a, b) => -Sorters.size(a.attachment, b.attachment));
         }
         break;
       case "waterfall":
         if (direction == "ascending") {
-          this.sortContents(this._byTiming);
+          this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
         } else {
-          this.sortContents((a, b) => !this._byTiming(a, b));
+          this.sortContents((a, b) => -Sorters.waterfall(a.attachment, b.attachment));
         }
         break;
     }
 
     this.refreshSummary();
     this.refreshZebra();
   },
 
@@ -866,86 +871,16 @@ RequestsMenuView.prototype = Heritage.ex
     $("#details-pane-toggle").disabled = true;
     $("#requests-menu-empty-notice").hidden = false;
 
     this.empty();
     this.refreshSummary();
   },
 
   /**
-   * Predicates used when sorting items.
-   *
-   * @param object aFirst
-   *        The first item used in the comparison.
-   * @param object aSecond
-   *        The second item used in the comparison.
-   * @return number
-   *         -1 to sort aFirst to a lower index than aSecond
-   *          0 to leave aFirst and aSecond unchanged with respect to each other
-   *          1 to sort aSecond to a lower index than aFirst
-   */
-  _byTiming: function ({ attachment: first }, { attachment: second }) {
-    return first.startedMillis > second.startedMillis;
-  },
-
-  _byStatus: function ({ attachment: first }, { attachment: second }) {
-    return first.status == second.status
-           ? first.startedMillis > second.startedMillis
-           : first.status > second.status;
-  },
-
-  _byMethod: function ({ attachment: first }, { attachment: second }) {
-    return first.method == second.method
-           ? first.startedMillis > second.startedMillis
-           : first.method > second.method;
-  },
-
-  _byFile: function ({ attachment: first }, { attachment: second }) {
-    let firstUrl = this._getUriNameWithQuery(first.url).toLowerCase();
-    let secondUrl = this._getUriNameWithQuery(second.url).toLowerCase();
-    return firstUrl == secondUrl
-      ? first.startedMillis > second.startedMillis
-      : firstUrl > secondUrl;
-  },
-
-  _byDomain: function ({ attachment: first }, { attachment: second }) {
-    let firstDomain = this._getUriHostPort(first.url).toLowerCase();
-    let secondDomain = this._getUriHostPort(second.url).toLowerCase();
-    return firstDomain == secondDomain
-      ? first.startedMillis > second.startedMillis
-      : firstDomain > secondDomain;
-  },
-
-  _byCause: function ({ attachment: first }, { attachment: second }) {
-    let firstCause = loadCauseString(first.cause.type);
-    let secondCause = loadCauseString(second.cause.type);
-
-    return firstCause == secondCause
-      ? first.startedMillis > second.startedMillis
-      : firstCause > secondCause;
-  },
-
-  _byType: function ({ attachment: first }, { attachment: second }) {
-    let firstType = this._getAbbreviatedMimeType(first.mimeType).toLowerCase();
-    let secondType = this._getAbbreviatedMimeType(second.mimeType).toLowerCase();
-
-    return firstType == secondType
-      ? first.startedMillis > second.startedMillis
-      : firstType > secondType;
-  },
-
-  _byTransferred: function ({ attachment: first }, { attachment: second }) {
-    return first.transferredSize > second.transferredSize;
-  },
-
-  _bySize: function ({ attachment: first }, { attachment: second }) {
-    return first.contentSize > second.contentSize;
-  },
-
-  /**
    * Refreshes the status displayed in this container's footer, providing
    * concise information about all requests.
    */
   refreshSummary: function () {
     let visibleItems = this.visibleItems;
     let visibleRequestsCount = visibleItems.length;
     if (!visibleRequestsCount) {
       this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
@@ -1340,19 +1275,19 @@ RequestsMenuView.prototype = Heritage.ex
       case "url": {
         let uri;
         try {
           uri = NetworkHelper.nsIURL(value);
         } catch (e) {
           // User input may not make a well-formed url yet.
           break;
         }
-        let nameWithQuery = this._getUriNameWithQuery(uri);
-        let hostPort = this._getUriHostPort(uri);
-        let host = this._getUriHost(uri);
+        let nameWithQuery = getUriNameWithQuery(uri);
+        let hostPort = getUriHostPort(uri);
+        let host = getUriHost(uri);
         let unicodeUrl = NetworkHelper.convertToUnicode(unescape(uri.spec));
 
         let file = $(".requests-menu-file", target);
         file.setAttribute("value", nameWithQuery);
         file.setAttribute("tooltiptext", unicodeUrl);
 
         let domain = $(".requests-menu-domain", target);
         domain.setAttribute("value", hostPort);
@@ -1459,17 +1394,17 @@ RequestsMenuView.prototype = Heritage.ex
           text = this.getFormattedSize(value);
         }
 
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", text);
         break;
       }
       case "mimeType": {
-        let type = this._getAbbreviatedMimeType(value);
+        let type = getAbbreviatedMimeType(value);
         let node = $(".requests-menu-type", target);
         let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
         node.setAttribute("value", text);
         node.setAttribute("tooltiptext", value);
         break;
       }
       case "responseContent": {
         let { mimeType } = item.attachment;
@@ -1989,58 +1924,16 @@ RequestsMenuView.prototype = Heritage.ex
    */
   _registerLastRequestEnd: function (unixTime) {
     if (this._lastRequestEndedMillis < unixTime) {
       this._lastRequestEndedMillis = unixTime;
     }
   },
 
   /**
-   * Helpers for getting details about an nsIURL.
-   *
-   * @param nsIURL | string url
-   * @return string
-   */
-  _getUriNameWithQuery: function (url) {
-    if (!(url instanceof Ci.nsIURL)) {
-      url = NetworkHelper.nsIURL(url);
-    }
-
-    let name = NetworkHelper.convertToUnicode(
-      unescape(url.fileName || url.filePath || "/"));
-    let query = NetworkHelper.convertToUnicode(unescape(url.query));
-
-    return name + (query ? "?" + query : "");
-  },
-
-  _getUriHostPort: function (url) {
-    if (!(url instanceof Ci.nsIURL)) {
-      url = NetworkHelper.nsIURL(url);
-    }
-    return NetworkHelper.convertToUnicode(unescape(url.hostPort));
-  },
-
-  _getUriHost: function (url) {
-    return this._getUriHostPort(url).replace(/:\d+$/, "");
-  },
-
-  /**
-   * Helper for getting an abbreviated string for a mime type.
-   *
-   * @param string mimeType
-   * @return string
-   */
-  _getAbbreviatedMimeType: function (mimeType) {
-    if (!mimeType) {
-      return "";
-    }
-    return (mimeType.split(";")[0].split("/")[1] || "").split("+")[0];
-  },
-
-  /**
    * Gets the total number of bytes representing the cumulated content size of
    * a set of requests. Returns 0 for an empty set.
    *
    * @param array itemsArray
    * @return number
    */
   _getTotalBytesOfRequests: function (itemsArray) {
     if (!itemsArray.length) {
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/sort-predicates.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const { getAbbreviatedMimeType,
+        getUriNameWithQuery,
+        getUriHostPort,
+        loadCauseString } = require("./request-utils");
+
+/**
+ * Predicates used when sorting items.
+ *
+ * @param object first
+ *        The first item used in the comparison.
+ * @param object second
+ *        The second item used in the comparison.
+ * @return number
+ *         <0 to sort first to a lower index than second
+ *         =0 to leave first and second unchanged with respect to each other
+ *         >0 to sort second to a lower index than first
+ */
+
+function waterfall(first, second) {
+  return first.startedMillis - second.startedMillis;
+}
+
+function status(first, second) {
+  return first.status == second.status
+         ? first.startedMillis - second.startedMillis
+         : first.status - second.status;
+}
+
+function method(first, second) {
+  if (first.method == second.method) {
+    return first.startedMillis - second.startedMillis;
+  }
+  return first.method > second.method ? 1 : -1;
+}
+
+function file(first, second) {
+  let firstUrl = getUriNameWithQuery(first.url).toLowerCase();
+  let secondUrl = getUriNameWithQuery(second.url).toLowerCase();
+  if (firstUrl == secondUrl) {
+    return first.startedMillis - second.startedMillis;
+  }
+  return firstUrl > secondUrl ? 1 : -1;
+}
+
+function domain(first, second) {
+  let firstDomain = getUriHostPort(first.url).toLowerCase();
+  let secondDomain = getUriHostPort(second.url).toLowerCase();
+  if (firstDomain == secondDomain) {
+    return first.startedMillis - second.startedMillis;
+  }
+  return firstDomain > secondDomain ? 1 : -1;
+}
+
+function cause(first, second) {
+  let firstCause = loadCauseString(first.cause.type);
+  let secondCause = loadCauseString(second.cause.type);
+  if (firstCause == secondCause) {
+    return first.startedMillis - second.startedMillis;
+  }
+  return firstCause > secondCause ? 1 : -1;
+}
+
+function type(first, second) {
+  let firstType = getAbbreviatedMimeType(first.mimeType).toLowerCase();
+  let secondType = getAbbreviatedMimeType(second.mimeType).toLowerCase();
+  if (firstType == secondType) {
+    return first.startedMillis - second.startedMillis;
+  }
+  return firstType > secondType ? 1 : -1;
+}
+
+function transferred(first, second) {
+  return first.transferredSize - second.transferredSize;
+}
+
+function size(first, second) {
+  return first.contentSize - second.contentSize;
+}
+
+exports.Sorters = {
+  status,
+  method,
+  file,
+  domain,
+  cause,
+  type,
+  transferred,
+  size,
+  waterfall,
+};
--- a/devtools/client/shared/components/moz.build
+++ b/devtools/client/shared/components/moz.build
@@ -11,16 +11,17 @@ DIRS += [
     'tree'
 ]
 
 DevToolsModules(
     'frame.js',
     'h-split-box.js',
     'notification-box.css',
     'notification-box.js',
+    'search-box.js',
     'sidebar-toggle.css',
     'sidebar-toggle.js',
     'stack-trace.js',
     'tree.js',
 )
 
 MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/search-box.js
@@ -0,0 +1,107 @@
+/* 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/. */
+
+/* global window */
+
+"use strict";
+
+const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
+
+/**
+ * A generic search box component for use across devtools
+ */
+module.exports = createClass({
+  displayName: "SearchBox",
+
+  propTypes: {
+    delay: PropTypes.number,
+    keyShortcut: PropTypes.string,
+    onChange: PropTypes.func,
+    placeholder: PropTypes.string,
+    type: PropTypes.string
+  },
+
+  getInitialState() {
+    return {
+      value: ""
+    };
+  },
+
+  componentDidMount() {
+    if (!this.props.keyShortcut) {
+      return;
+    }
+
+    this.shortcuts = new KeyShortcuts({
+      window
+    });
+    this.shortcuts.on(this.props.keyShortcut, (name, event) => {
+      event.preventDefault();
+      this.refs.input.focus();
+    });
+  },
+
+  componentWillUnmount() {
+    this.shortcuts.destroy();
+    // Clean up an existing timeout.
+    if (this.searchTimeout) {
+      clearTimeout(this.searchTimeout);
+    }
+  },
+
+  onChange() {
+    if (this.state.value !== this.refs.input.value) {
+      this.setState({ value: this.refs.input.value });
+    }
+
+    if (!this.props.delay) {
+      this.props.onChange(this.state.value);
+      return;
+    }
+
+    // Clean up an existing timeout before creating a new one.
+    if (this.searchTimeout) {
+      clearTimeout(this.searchTimeout);
+    }
+
+    // Execute the search after a timeout. It makes the UX
+    // smoother if the user is typing quickly.
+    this.searchTimeout = setTimeout(() => {
+      this.searchTimeout = null;
+      this.props.onChange(this.state.value);
+    }, this.props.delay);
+  },
+
+  onClearButtonClick() {
+    this.refs.input.value = "";
+    this.onChange();
+  },
+
+  render() {
+    let { type = "search", placeholder } = this.props;
+    let { value } = this.state;
+    let divClassList = ["devtools-searchbox", "has-clear-btn"];
+    let inputClassList = [`devtools-${type}input`];
+
+    if (value !== "") {
+      inputClassList.push("filled");
+    }
+    return dom.div(
+      { className: divClassList.join(" ") },
+      dom.input({
+        className: inputClassList.join(" "),
+        onChange: this.onChange,
+        placeholder,
+        ref: "input",
+        value
+      }),
+      dom.button({
+        className: "devtools-searchinput-clear",
+        hidden: value == "",
+        onClick: this.onClearButtonClick
+      })
+    );
+  }
+});
--- a/devtools/client/themes/images/filters.svg
+++ b/devtools/client/themes/images/filters.svg
@@ -14,18 +14,18 @@
     <feColorMatrix in="SourceGraphic" type="matrix"
       values="0 0 0 0 0
               0 0 0 0 1
               0 0 0 0 0.212
               0 0 0 1 0"/>
   </filter>
 
   <!-- Web Audio Gradients -->
-  <linearGradient id="bypass-light" x1="6%" y1="8%" x2="12%" y2="12%" spreadMethod="repeat">
-    <stop offset="0%" stop-color="#f0f1f2"/> <!-- theme-toolbar-background -->
-    <stop offset="50%" stop-color="#fff"/>
+  <linearGradient id="bypass-light" x1="8%" y1="10%" x2="16%" y2="16%" spreadMethod="repeat">
+    <stop offset="0%" stop-color="#dde1e4a0"/> <!-- theme-splitter-color (0.5 opacity) -->
+    <stop offset="50%" stop-color="transparent"/>
   </linearGradient>
 
-  <linearGradient id="bypass-dark" x1="6%" y1="8%" x2="12%" y2="12%" spreadMethod="repeat">
-    <stop offset="0%" stop-color="#343c45"/> <!-- theme-toolbar-background -->
+  <linearGradient id="bypass-dark" x1="8%" y1="10%" x2="16%" y2="16%" spreadMethod="repeat">
+    <stop offset="0%" stop-color="#454d5d"/> <!-- theme-splitter-color -->
     <stop offset="50%" stop-color="transparent"/>
   </linearGradient>
 </svg>
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -590,50 +590,48 @@ html, body, #app, #memory-tool {
   text-align: center;
   padding: 5px;
 }
 
 /**
  * Dagre-D3 graphs
  */
 
+svg {
+  --arrow-color: var(--theme-splitter-color);
+  --text-color: var(--theme-body-color-alt);
+}
+
+.theme-dark svg {
+  --arrow-color: var(--theme-body-color-alt);
+}
+
+svg #arrowhead {
+  /* !important is needed to override inline style */
+  fill: var(--arrow-color) !important;
+}
+
 .edgePath path {
   stroke-width: 1px;
   fill: none;
-}
-
-.theme-dark .edgePath path {
-  stroke: var(--theme-body-color-alt);
-}
-.theme-light .edgePath path {
-  stroke: var(--theme-splitter-color);
+  stroke: var(--arrow-color);
 }
 
 g.edgeLabel rect {
   fill: var(--theme-body-background);
 }
+
 g.edgeLabel tspan {
-  fill: var(--theme-body-color-alt);
+  fill: var(--text-color);
 }
 
 .nodes rect {
   stroke-width: 1px;
-}
-
-.nodes rect {
-  stroke: var(--theme-tab-toolbar-background);
-}
-.theme-light rect {
-  fill: var(--theme-tab-toolbar-background);
-}
-.theme-dark rect {
+  stroke: var(--theme-splitter-color);
   fill: var(--theme-toolbar-background);
 }
 
 text {
-  font-weight: 300;
-  font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
-  font-size: 14px;
+  font-size: 1.25em;
+  fill: var(--text-color);
+  /* Make sure text stays inside its container in RTL locales */
+  direction: ltr;
 }
-
-text {
-  fill: var(--theme-body-color-alt);
-}
--- a/devtools/client/themes/webaudioeditor.css
+++ b/devtools/client/themes/webaudioeditor.css
@@ -15,107 +15,100 @@
 #waiting-notice {
   font-size: 110%;
 }
 
 /* Context Graph */
 svg {
   overflow: hidden;
   -moz-box-flex: 1;
+  --arrow-color: var(--theme-splitter-color);
+  --text-color: var(--theme-body-color-alt);
+}
+
+.theme-dark svg {
+  --arrow-color: var(--theme-body-color-alt);
 }
 
 /* Edges in graph */
 .edgePath path {
   stroke-width: 1px;
-  fill: none;
+  stroke: var(--arrow-color);
 }
-
-.theme-dark .edgePath path {
-  stroke: var(--theme-body-color-alt);
-}
-.theme-light .edgePath path {
-  stroke: var(--theme-splitter-color);
+svg #arrowhead {
+  /* !important is needed to override inline style */
+  fill: var(--arrow-color) !important;
 }
 
 /* AudioParam connection edges */
-g.edgePath.param-connection {
+g.edgePath.param-connection path {
   stroke-dasharray: 5,5;
-}
-
-.theme-dark .edgePath.param-connection path {
-  stroke: var(--theme-body-color-alt);
-}
-.theme-light .edgePath.param-connection path {
-  stroke: var(--theme-splitter-color);
+  stroke: var(--arrow-colo);
 }
 
 /* Labels in AudioParam connection should have background that match
  * the main background so there's whitespace around the label, on top of the
  * dotted lines. */
 g.edgeLabel rect {
   fill: var(--theme-body-background);
 }
 g.edgeLabel tspan {
-  fill: var(--theme-body-color-alt);
+  fill: var(--text-color);
 }
 
 /* Audio Nodes */
 .nodes rect {
   stroke-width: 1px;
   cursor: pointer;
-  stroke: var(--theme-tab-toolbar-background);
+  stroke: var(--theme-splitter-color);
   fill: var(--theme-toolbar-background);
 }
 
 /**
  * Bypassed Nodes
  */
 
 .theme-light .nodes g.bypassed rect {
   fill: url(chrome://devtools/skin/images/filters.svg#bypass-light);
 }
+
 .theme-dark .nodes g.bypassed rect {
   fill: url(chrome://devtools/skin/images/filters.svg#bypass-dark);
 }
+
 .nodes g.bypassed.selected rect {
   stroke: var(--theme-selection-background);
 }
 
-/*
 .nodes g.bypassed text {
-  opacity: 0.8;
+  opacity: 0.6;
 }
-*/
 
 /**
  * Selected Nodes
  */
 .nodes g.selected rect {
   fill: var(--theme-selection-background);
 }
 
-/* Don't style bypassed nodes text different because it'd be illegible in light-theme */
-.theme-light g.selected:not(.bypassed) text {
-  fill: var(--theme-toolbar-background);
+/* Don't style bypassed nodes text differently because it'd be illegible in light-theme */
+g.selected:not(.bypassed) text {
+  fill: var(--theme-selection-color);
 }
 
 
 /* Text in nodes and edges */
 text {
   cursor: default; /* override the "text" cursor */
-  font-weight: 300;
-  font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
-  font-size: 14px;
+  fill: var(--text-color);
+  font-size: 1.25em;
+  /* Make sure text stays inside its container in RTL locales */
+  direction: ltr;
 }
 
-text {
-  fill: var(--theme-body-color-alt);
-}
-
-
 .nodes text {
   cursor: pointer;
 }
 
 /**
  * Inspector Styles
  */
 
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -67,44 +67,50 @@ NewConsoleOutputWrapper.prototype = {
       React.DOM.div(
         {className: "webconsole-output-wrapper"},
         filterBar,
         childComponent
     ));
 
     this.body = ReactDOM.render(provider, this.parentNode);
   },
-  dispatchMessageAdd: function(message, waitForResponse) {
-      let action = actions.messageAdd(message);
-      let messageId = action.message.get("id");
-      batchedMessageAdd(action);
+
+  dispatchMessageAdd: function (message, waitForResponse) {
+    let action = actions.messageAdd(message);
+    batchedMessageAdd(action);
 
-      // Wait for the message to render to resolve with the DOM node.
-      // This is just for backwards compatibility with old tests, and should
-      // be removed once it's not needed anymore.
-      if (waitForResponse) {
-        return new Promise(resolve => {
-          let jsterm = this.jsterm;
-          jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
-            for (let m of messages) {
-              if (m.messageId == messageId) {
-                resolve(m.node);
-                jsterm.hud.off("new-messages", onThisMessage);
-                return;
-              }
+    // Wait for the message to render to resolve with the DOM node.
+    // This is just for backwards compatibility with old tests, and should
+    // be removed once it's not needed anymore.
+    // Can only wait for response if the action contains a valid message.
+    if (waitForResponse && action.message) {
+      let messageId = action.message.get("id");
+      return new Promise(resolve => {
+        let jsterm = this.jsterm;
+        jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
+          for (let m of messages) {
+            if (m.messageId == messageId) {
+              resolve(m.node);
+              jsterm.hud.off("new-messages", onThisMessage);
+              return;
             }
-          });
+          }
         });
-      }
+      });
+    }
+
+    return Promise.resolve();
   },
-  dispatchMessagesAdd: function(messages) {
+
+  dispatchMessagesAdd: function (messages) {
     const batchedActions = messages.map(message => actions.messageAdd(message));
     store.dispatch(actions.batchActions(batchedActions));
   },
-  dispatchMessagesClear: function() {
+
+  dispatchMessagesClear: function () {
     store.dispatch(actions.messagesClear());
   },
 };
 
 function batchedMessageAdd(action) {
   queuedActions.push(action);
   if (!throttledDispatchTimeout) {
     throttledDispatchTimeout = setTimeout(() => {
--- a/devtools/shared/css/generated/generate-properties-db.js
+++ b/devtools/shared/css/generated/generate-properties-db.js
@@ -6,25 +6,27 @@
 /*
  * This is an xpcshell script that runs to generate a static list of CSS properties
  * as known by the platform. It is run from ./mach_commands.py by running
  * `mach devtools-css-db`.
  */
 var {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
 var {generateCssProperties} = require("devtools/server/actors/css-properties");
 
+// xpcshell can output extra information, so place some delimiter text between
+// the output of the css properties database.
+dump("DEVTOOLS_CSS_DB_DELIMITER");
+
 // Output JSON
 dump(JSON.stringify({
   cssProperties: cssProperties(),
   pseudoElements: pseudoElements()
 }));
-// In a debug build, xpcshell might print extra debugging information,
-// so we emit a trailing newline and then arrange to just read a
-// single (long) line of JSON from the output.
-dump("\n");
+
+dump("DEVTOOLS_CSS_DB_DELIMITER");
 
 /*
  * A list of CSS Properties and their various characteristics. This is used on the
  * client-side when the CssPropertiesActor is not found, or when the client and server
  * are the same version. A single property takes the form:
  *
  *  "animation": {
  *    "isInherited": false,
--- a/devtools/shared/css/generated/mach_commands.py
+++ b/devtools/shared/css/generated/mach_commands.py
@@ -1,26 +1,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 """
-This script implements the `mach devtools-css-db` command. It runs the C preprocessor
-on the CSS properties header file to get the list of preferences associated with
-a specific property, and it runs an xpcshell script that uses inIDOMUtils to query
-the CSS properties used by the browser. This information is used to generate the
-properties-db.js file.
+This script implements the `mach devtools-css-db` command. It runs an xpcshell script
+that uses inIDOMUtils to query the CSS properties used by the browser. This information
+is used to generate the properties-db.js file.
 """
 
 import json
 import os
 import sys
 import string
 import subprocess
-from mozbuild import shellutil
 from mozbuild.base import (
     MozbuildObject,
     MachCommandBase,
 )
 from mach.decorators import (
     CommandProvider,
     Command,
 )
@@ -37,56 +34,22 @@ def stringify(obj):
 class MachCommands(MachCommandBase):
     @Command(
         'devtools-css-db', category='post-build',
         description='Rebuild the devtool\'s static css properties database.')
     def generate_css_db(self):
         """Generate the static css properties database for devtools and write it to file."""
 
         print("Re-generating the css properties database...")
-        preferences = self.get_preferences()
         db = self.get_properties_db_from_xpcshell()
 
         self.output_template({
-            'preferences': stringify(preferences),
             'cssProperties': stringify(db['cssProperties']),
             'pseudoElements': stringify(db['pseudoElements'])})
 
-    def get_preferences(self):
-        """Get all of the preferences associated with enabling and disabling a property."""
-        # Build the command to run the preprocessor on PythonCSSProps.h
-        headerPath = resolve_path(self.topsrcdir, 'layout/style/PythonCSSProps.h')
-
-        cpp = self.substs['CPP']
-
-        if not cpp:
-            print("Unable to find the cpp program. Please do a full, non-artifact")
-            print("build and try this again.")
-            sys.exit(1)
-
-        if type(cpp) is list:
-            cmd = cpp
-        else:
-            cmd = shellutil.split(cpp)
-        cmd += shellutil.split(self.substs['ACDEFINES'])
-        cmd.append(headerPath)
-
-        # The preprocessed list takes the following form:
-        # [ (name, prop, id, flags, pref, proptype), ... ]
-        preprocessed = eval(subprocess.check_output(cmd))
-
-        # Map this list
-        # (name, prop, id, flags, pref, proptype) => (name, pref)
-        preferences = [
-            (name, pref)
-            for name, prop, id, flags, pref, proptype in preprocessed
-            if 'CSS_PROPERTY_INTERNAL' not in flags]
-
-        return preferences
-
     def get_properties_db_from_xpcshell(self):
         """Generate the static css properties db for devtools from an xpcshell script."""
         build = MozbuildObject.from_environment()
 
         # Get the paths
         script_path = resolve_path(self.topsrcdir,
             'devtools/shared/css/generated/generate-properties-db.js')
         gre_path = resolve_path(self.topobjdir, 'dist/bin')
@@ -98,19 +61,19 @@ class MachCommands(MachCommandBase):
         if sys.platform.startswith('linux'):
             sub_env["LD_LIBRARY_PATH"] = gre_path
 
         # Run the xcpshell script, and set the appdir flag to the browser path so that
         # we have the proper dependencies for requiring the loader.
         contents = subprocess.check_output([xpcshell_path, '-g', gre_path,
                                             '-a', browser_path, script_path],
                                            env = sub_env)
-        # Extract just the first line of output, since a debug-build
-        # xpcshell might emit extra output that we don't want.
-        contents = contents.split('\n')[0]
+        # Extract just the output between the delimiters as the xpcshell output can
+        # have extra output that we don't want.
+        contents = contents.split('DEVTOOLS_CSS_DB_DELIMITER')[1]
 
         return json.loads(contents)
 
     def output_template(self, substitutions):
         """Output a the properties-db.js from a template."""
         js_template_path = resolve_path(self.topsrcdir,
             'devtools/shared/css/generated/properties-db.js.in')
         destination_path = resolve_path(self.topsrcdir,
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -9297,1839 +9297,8 @@ exports.PSEUDO_ELEMENTS = [
   ":-moz-range-track",
   ":-moz-range-progress",
   ":-moz-range-thumb",
   ":-moz-meter-bar",
   ":-moz-placeholder",
   ":placeholder",
   ":-moz-color-swatch"
 ];
-
-/**
- * A list of the preferences keys for whether a CSS property is enabled or not. This is
- * exposed for testing purposes.
- */
-exports.PREFERENCES = [
-  [
-    "align-content",
-    ""
-  ],
-  [
-    "align-items",
-    ""
-  ],
-  [
-    "align-self",
-    ""
-  ],
-  [
-    "all",
-    "layout.css.all-shorthand.enabled"
-  ],
-  [
-    "animation",
-    ""
-  ],
-  [
-    "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",
-    ""
-  ],
-  [
-    "background-attachment",
-    ""
-  ],
-  [
-    "background-blend-mode",
-    "layout.css.background-blend-mode.enabled"
-  ],
-  [
-    "background-clip",
-    ""
-  ],
-  [
-    "background-color",
-    ""
-  ],
-  [
-    "background-image",
-    ""
-  ],
-  [
-    "background-origin",
-    ""
-  ],
-  [
-    "background-position",
-    ""
-  ],
-  [
-    "background-position-x",
-    ""
-  ],
-  [
-    "background-position-y",
-    ""
-  ],
-  [
-    "background-repeat",
-    ""
-  ],
-  [
-    "background-size",
-    ""
-  ],
-  [
-    "-moz-binding",
-    ""
-  ],
-  [
-    "block-size",
-    ""
-  ],
-  [
-    "border",
-    ""
-  ],
-  [
-    "border-block-end",
-    ""
-  ],
-  [
-    "border-block-end-color",
-    ""
-  ],
-  [
-    "border-block-end-style",
-    ""
-  ],
-  [
-    "border-block-end-width",
-    ""
-  ],
-  [
-    "border-block-start",
-    ""
-  ],
-  [
-    "border-block-start-color",
-    ""
-  ],
-  [
-    "border-block-start-style",
-    ""
-  ],
-  [
-    "border-block-start-width",
-    ""
-  ],
-  [
-    "border-bottom",
-    ""
-  ],
-  [
-    "border-bottom-color",
-    ""
-  ],
-  [
-    "-moz-border-bottom-colors",
-    ""
-  ],
-  [
-    "border-bottom-left-radius",
-    ""
-  ],
-  [
-    "border-bottom-right-radius",
-    ""
-  ],
-  [
-    "border-bottom-style",
-    ""
-  ],
-  [
-    "border-bottom-width",
-    ""
-  ],
-  [
-    "border-collapse",
-    ""
-  ],
-  [
-    "border-color",
-    ""
-  ],
-  [
-    "border-image",
-    ""
-  ],
-  [
-    "border-image-outset",
-    ""
-  ],
-  [
-    "border-image-repeat",
-    ""
-  ],
-  [
-    "border-image-slice",
-    ""
-  ],
-  [
-    "border-image-source",
-    ""
-  ],
-  [
-    "border-image-width",
-    ""
-  ],
-  [
-    "border-inline-end",
-    ""
-  ],
-  [
-    "border-inline-end-color",
-    ""
-  ],
-  [
-    "border-inline-end-style",
-    ""
-  ],
-  [
-    "border-inline-end-width",
-    ""
-  ],
-  [
-    "border-inline-start",
-    ""
-  ],
-  [
-    "border-inline-start-color",
-    ""
-  ],
-  [
-    "border-inline-start-style",
-    ""
-  ],
-  [
-    "border-inline-start-width",
-    ""
-  ],
-  [
-    "border-left",
-    ""
-  ],
-  [
-    "border-left-color",
-    ""
-  ],
-  [
-    "-moz-border-left-colors",
-    ""
-  ],
-  [
-    "border-left-style",
-    ""
-  ],
-  [
-    "border-left-width",
-    ""
-  ],
-  [
-    "border-radius",
-    ""
-  ],
-  [
-    "border-right",
-    ""
-  ],
-  [
-    "border-right-color",
-    ""
-  ],
-  [
-    "-moz-border-right-colors",
-    ""
-  ],
-  [
-    "border-right-style",
-    ""
-  ],
-  [
-    "border-right-width",
-    ""
-  ],
-  [
-    "border-spacing",
-    ""
-  ],
-  [
-    "border-style",
-    ""
-  ],
-  [
-    "border-top",
-    ""
-  ],
-  [
-    "border-top-color",
-    ""
-  ],
-  [
-    "-moz-border-top-colors",
-    ""
-  ],
-  [
-    "border-top-left-radius",
-    ""
-  ],
-  [
-    "border-top-right-radius",
-    ""
-  ],
-  [
-    "border-top-style",
-    ""
-  ],
-  [
-    "border-top-width",
-    ""
-  ],
-  [
-    "border-width",
-    ""
-  ],
-  [
-    "bottom",
-    ""
-  ],
-  [
-    "-moz-box-align",
-    ""
-  ],
-  [
-    "box-decoration-break",
-    "layout.css.box-decoration-break.enabled"
-  ],
-  [
-    "-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",
-    "layout.css.color-adjust.enabled"
-  ],
-  [
-    "color-interpolation",
-    ""
-  ],
-  [
-    "color-interpolation-filters",
-    ""
-  ],
-  [
-    "column-count",
-    ""
-  ],
-  [
-    "column-fill",
-    ""
-  ],
-  [
-    "column-gap",
-    ""
-  ],
-  [
-    "column-rule",
-    ""
-  ],
-  [
-    "column-rule-color",
-    ""
-  ],
-  [
-    "column-rule-style",
-    ""
-  ],
-  [
-    "column-rule-width",
-    ""
-  ],
-  [
-    "column-width",
-    ""
-  ],
-  [
-    "columns",
-    ""
-  ],
-  [
-    "contain",
-    "layout.css.contain.enabled"
-  ],
-  [
-    "content",
-    ""
-  ],
-  [
-    "counter-increment",
-    ""
-  ],
-  [
-    "counter-reset",
-    ""
-  ],
-  [
-    "cursor",
-    ""
-  ],
-  [
-    "direction",
-    ""
-  ],
-  [
-    "display",
-    ""
-  ],
-  [
-    "dominant-baseline",
-    ""
-  ],
-  [
-    "empty-cells",
-    ""
-  ],
-  [
-    "fill",
-    ""
-  ],
-  [
-    "fill-opacity",
-    ""
-  ],
-  [
-    "fill-rule",
-    ""
-  ],
-  [
-    "filter",
-    ""
-  ],
-  [
-    "flex",
-    ""
-  ],
-  [
-    "flex-basis",
-    ""
-  ],
-  [
-    "flex-direction",
-    ""
-  ],
-  [
-    "flex-flow",
-    ""
-  ],
-  [
-    "flex-grow",
-    ""
-  ],
-  [
-    "flex-shrink",
-    ""
-  ],
-  [
-    "flex-wrap",
-    ""
-  ],
-  [
-    "float",
-    ""
-  ],
-  [
-    "-moz-float-edge",
-    ""
-  ],
-  [
-    "flood-color",
-    ""
-  ],
-  [
-    "flood-opacity",
-    ""
-  ],
-  [
-    "font",
-    ""
-  ],
-  [
-    "font-family",
-    ""
-  ],
-  [
-    "font-feature-settings",
-    ""
-  ],
-  [
-    "font-kerning",
-    ""
-  ],
-  [
-    "font-language-override",
-    ""
-  ],
-  [
-    "font-size",
-    ""
-  ],
-  [
-    "font-size-adjust",
-    ""
-  ],
-  [
-    "font-stretch",
-    ""
-  ],
-  [
-    "font-style",
-    ""
-  ],
-  [
-    "font-synthesis",
-    ""
-  ],
-  [
-    "font-variant",
-    ""
-  ],
-  [
-    "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",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-area",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-auto-columns",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-auto-flow",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-auto-rows",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-column",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-column-end",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-column-gap",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-column-start",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-gap",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-row",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-row-end",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-row-gap",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-row-start",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-template",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-template-areas",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-template-columns",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "grid-template-rows",
-    "layout.css.grid.enabled"
-  ],
-  [
-    "height",
-    ""
-  ],
-  [
-    "hyphens",
-    ""
-  ],
-  [
-    "initial-letter",
-    "layout.css.initial-letter.enabled"
-  ],
-  [
-    "image-orientation",
-    "layout.css.image-orientation.enabled"
-  ],
-  [
-    "-moz-image-region",
-    ""
-  ],
-  [
-    "image-rendering",
-    ""
-  ],
-  [
-    "ime-mode",
-    ""
-  ],
-  [
-    "inline-size",
-    ""
-  ],
-  [
-    "isolation",
-    "layout.css.isolation.enabled"
-  ],
-  [
-    "justify-content",
-    ""
-  ],
-  [
-    "justify-items",
-    ""
-  ],
-  [
-    "justify-self",
-    ""
-  ],
-  [
-    "left",
-    ""
-  ],
-  [
-    "letter-spacing",
-    ""
-  ],
-  [
-    "lighting-color",
-    ""
-  ],
-  [
-    "line-height",
-    ""
-  ],
-  [
-    "list-style",
-    ""
-  ],
-  [
-    "list-style-image",
-    ""
-  ],
-  [
-    "list-style-position",
-    ""
-  ],
-  [
-    "list-style-type",
-    ""
-  ],
-  [
-    "margin",
-    ""
-  ],
-  [
-    "margin-block-end",
-    ""
-  ],
-  [
-    "margin-block-start",
-    ""
-  ],
-  [
-    "margin-bottom",
-    ""
-  ],
-  [
-    "margin-inline-end",
-    ""
-  ],
-  [
-    "margin-inline-start",
-    ""
-  ],
-  [
-    "margin-left",
-    ""
-  ],
-  [
-    "margin-right",
-    ""
-  ],
-  [
-    "margin-top",
-    ""
-  ],
-  [
-    "marker",
-    ""
-  ],
-  [
-    "marker-end",
-    ""
-  ],
-  [
-    "marker-mid",
-    ""
-  ],
-  [
-    "marker-offset",
-    ""
-  ],
-  [
-    "marker-start",
-    ""
-  ],
-  [
-    "mask",
-    ""
-  ],
-  [
-    "mask-clip",
-    ""
-  ],
-  [
-    "mask-composite",
-    ""
-  ],
-  [
-    "mask-image",
-    ""
-  ],
-  [
-    "mask-mode",
-    ""
-  ],
-  [
-    "mask-origin",
-    ""
-  ],
-  [
-    "mask-position",
-    ""
-  ],
-  [
-    "mask-position-x",
-    ""
-  ],
-  [
-    "mask-position-y",
-    ""
-  ],
-  [
-    "mask-repeat",
-    ""
-  ],
-  [
-    "mask-size",
-    ""
-  ],
-  [
-    "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",
-    "layout.css.mix-blend-mode.enabled"
-  ],
-  [
-    "object-fit",
-    "layout.css.object-fit-and-position.enabled"
-  ],
-  [
-    "object-position",
-    "layout.css.object-fit-and-position.enabled"
-  ],
-  [
-    "offset-block-end",
-    ""
-  ],
-  [
-    "offset-block-start",
-    ""
-  ],
-  [
-    "offset-inline-end",
-    ""
-  ],
-  [
-    "offset-inline-start",
-    ""
-  ],
-  [
-    "opacity",
-    ""
-  ],
-  [
-    "order",
-    ""
-  ],
-  [
-    "-moz-orient",
-    ""
-  ],
-  [
-    "-moz-osx-font-smoothing",
-    "layout.css.osx-font-smoothing.enabled"
-  ],
-  [
-    "outline",
-    ""
-  ],
-  [
-    "outline-color",
-    ""
-  ],
-  [
-    "outline-offset",
-    ""
-  ],
-  [
-    "-moz-outline-radius",
-    ""
-  ],
-  [
-    "-moz-outline-radius-bottomleft",
-    ""
-  ],
-  [
-    "-moz-outline-radius-bottomright",
-    ""
-  ],
-  [
-    "-moz-outline-radius-topleft",
-    ""
-  ],
-  [
-    "-moz-outline-radius-topright",
-    ""
-  ],
-  [
-    "outline-style",
-    ""
-  ],
-  [
-    "outline-width",
-    ""
-  ],
-  [
-    "overflow",
-    ""
-  ],
-  [
-    "overflow-clip-box",
-    "layout.css.overflow-clip-box.enabled"
-  ],
-  [
-    "overflow-x",
-    ""
-  ],
-  [
-    "overflow-y",
-    ""
-  ],
-  [
-    "padding",
-    ""
-  ],
-  [
-    "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",
-    "svg.paint-order.enabled"
-  ],
-  [
-    "perspective",
-    ""
-  ],
-  [
-    "perspective-origin",
-    ""
-  ],
-  [
-    "pointer-events",
-    ""
-  ],
-  [
-    "position",
-    ""
-  ],
-  [
-    "quotes",
-    ""
-  ],
-  [
-    "resize",
-    ""
-  ],
-  [
-    "right",
-    ""
-  ],
-  [
-    "ruby-align",
-    ""
-  ],
-  [
-    "ruby-position",
-    ""
-  ],
-  [
-    "scroll-behavior",
-    "layout.css.scroll-behavior.property-enabled"
-  ],
-  [
-    "scroll-snap-coordinate",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "scroll-snap-destination",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "scroll-snap-points-x",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "scroll-snap-points-y",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "scroll-snap-type",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "scroll-snap-type-x",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "scroll-snap-type-y",
-    "layout.css.scroll-snap.enabled"
-  ],
-  [
-    "shape-outside",
-    "layout.css.shape-outside.enabled"
-  ],
-  [
-    "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",
-    ""
-  ],
-  [
-    "text-align-last",
-    ""
-  ],
-  [
-    "text-anchor",
-    ""
-  ],
-  [
-    "text-combine-upright",
-    "layout.css.text-combine-upright.enabled"
-  ],
-  [
-    "text-decoration",
-    ""
-  ],
-  [
-    "text-decoration-color",
-    ""
-  ],
-  [
-    "text-decoration-line",
-    ""
-  ],
-  [
-    "text-decoration-style",
-    ""
-  ],
-  [
-    "text-emphasis",
-    ""
-  ],
-  [
-    "text-emphasis-color",
-    ""
-  ],
-  [
-    "text-emphasis-position",
-    ""
-  ],
-  [
-    "text-emphasis-style",
-    ""
-  ],
-  [
-    "-webkit-text-fill-color",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "text-indent",
-    ""
-  ],
-  [
-    "text-orientation",
-    ""
-  ],
-  [
-    "text-overflow",
-    ""
-  ],
-  [
-    "text-rendering",
-    ""
-  ],
-  [
-    "text-shadow",
-    ""
-  ],
-  [
-    "-moz-text-size-adjust",
-    ""
-  ],
-  [
-    "-webkit-text-stroke",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-text-stroke-color",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-text-stroke-width",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "text-transform",
-    ""
-  ],
-  [
-    "top",
-    ""
-  ],
-  [
-    "touch-action",
-    "layout.css.touch_action.enabled"
-  ],
-  [
-    "transform",
-    ""
-  ],
-  [
-    "-moz-transform",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "transform-box",
-    "svg.transform-box.enabled"
-  ],
-  [
-    "transform-origin",
-    ""
-  ],
-  [
-    "transform-style",
-    ""
-  ],
-  [
-    "transition",
-    ""
-  ],
-  [
-    "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",
-    ""
-  ],
-  [
-    "overflow-wrap",
-    ""
-  ],
-  [
-    "writing-mode",
-    ""
-  ],
-  [
-    "z-index",
-    ""
-  ],
-  [
-    "word-wrap",
-    ""
-  ],
-  [
-    "-moz-transform-origin",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-perspective-origin",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-perspective",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-transform-style",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-backface-visibility",
-    "layout.css.prefixes.transforms"
-  ],
-  [
-    "-moz-border-image",
-    "layout.css.prefixes.border-image"
-  ],
-  [
-    "-moz-transition",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-delay",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-duration",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-property",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-transition-timing-function",
-    "layout.css.prefixes.transitions"
-  ],
-  [
-    "-moz-animation",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-delay",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-direction",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-duration",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-fill-mode",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-iteration-count",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-name",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-play-state",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-animation-timing-function",
-    "layout.css.prefixes.animations"
-  ],
-  [
-    "-moz-box-sizing",
-    "layout.css.prefixes.box-sizing"
-  ],
-  [
-    "-moz-font-feature-settings",
-    "layout.css.prefixes.font-features"
-  ],
-  [
-    "-moz-font-language-override",
-    "layout.css.prefixes.font-features"
-  ],
-  [
-    "-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",
-    ""
-  ],
-  [
-    "-moz-text-align-last",
-    ""
-  ],
-  [
-    "-moz-column-count",
-    ""
-  ],
-  [
-    "-moz-column-fill",
-    ""
-  ],
-  [
-    "-moz-column-gap",
-    ""
-  ],
-  [
-    "-moz-column-rule",
-    ""
-  ],
-  [
-    "-moz-column-rule-color",
-    ""
-  ],
-  [
-    "-moz-column-rule-style",
-    ""
-  ],
-  [
-    "-moz-column-rule-width",
-    ""
-  ],
-  [
-    "-moz-column-width",
-    ""
-  ],
-  [
-    "-moz-columns",
-    ""
-  ],
-  [
-    "-webkit-animation",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-delay",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-direction",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-duration",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-fill-mode",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-iteration-count",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-name",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-play-state",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-animation-timing-function",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-filter",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-text-size-adjust",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transform",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transform-origin",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transform-style",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-backface-visibility",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-perspective",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-perspective-origin",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-delay",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-duration",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-property",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-transition-timing-function",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-top-left-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-top-right-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-bottom-left-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-bottom-right-radius",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-background-clip",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-background-origin",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-background-size",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-border-image",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-shadow",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-sizing",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-flex",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-ordinal-group",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-orient",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-direction",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-align",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-box-pack",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex-direction",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex-wrap",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex-flow",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-order",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex-grow",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex-shrink",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-flex-basis",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-justify-content",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-align-items",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-align-self",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-align-content",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-user-select",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-clip",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-composite",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-image",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-origin",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-position",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-position-x",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-position-y",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-repeat",
-    "layout.css.prefixes.webkit"
-  ],
-  [
-    "-webkit-mask-size",
-    "layout.css.prefixes.webkit"
-  ]
-];
--- a/devtools/shared/css/generated/properties-db.js.in
+++ b/devtools/shared/css/generated/properties-db.js.in
@@ -13,14 +13,8 @@
  * A list of CSS Properties and their various characteristics.
  */
 exports.CSS_PROPERTIES = ${cssProperties};
 
 /**
  * A list of the pseudo elements.
  */
 exports.PSEUDO_ELEMENTS = ${pseudoElements};
-
-/**
- * A list of the preferences keys for whether a CSS property is enabled or not. This is
- * exposed for testing purposes.
- */
-exports.PREFERENCES = ${preferences};
--- a/devtools/shared/css/properties-db.js
+++ b/devtools/shared/css/properties-db.js
@@ -5,17 +5,30 @@
 "use strict";
 
 /**
  * This file contains static lists of CSS properties and values. Some of the small lists
  * are edited manually, while the larger ones are generated by a script. The comments
  * above each list indicates how it should be updated.
  */
 
-const { CSS_PROPERTIES, PSEUDO_ELEMENTS } = require("devtools/shared/css/generated/properties-db");
+let db;
+
+// Allow this require to fail in case it's been deleted in the process of running
+// `mach devtools-css-db` to regenerate the database.
+try {
+  db = require("devtools/shared/css/generated/properties-db");
+} catch (error) {
+  console.error(`If this error is being displayed and "mach devtools-css-db" is not ` +
+                `being run, then it needs to be fixed.`, error);
+  db = {
+    CSS_PROPERTIES: {},
+    PSEUDO_ELEMENTS: []
+  };
+}
 
 /**
  * All CSS types that properties can support. This list can be manually edited.
  */
 exports.CSS_TYPES = {
   "ANGLE": 1,
   "COLOR": 2,
   "FREQUENCY": 3,
@@ -52,26 +65,26 @@ exports.ANGLE_TAKING_FUNCTIONS = ["linea
                                   "rotateY", "rotateZ", "rotate3d", "skew", "skewX",
                                   "skewY", "hue-rotate"];
 
 /**
  * The list of all CSS Pseudo Elements.
  *
  * This list can be updated with `mach devtools-css-db`.
  */
-exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS;
+exports.PSEUDO_ELEMENTS = db.PSEUDO_ELEMENTS;
 
 /**
  * A list of CSS Properties and their various characteristics. This is used on the
  * client-side when the CssPropertiesActor is not found, or when the client and server
  * are the same version. A single property takes the form:
  *
  *  "animation": {
  *    "isInherited": false,
  *    "supports": [ 7, 9, 10 ]
  *  }
  */
-exports.CSS_PROPERTIES = CSS_PROPERTIES;
+exports.CSS_PROPERTIES = db.CSS_PROPERTIES;
 
 exports.CSS_PROPERTIES_DB = {
-  properties: CSS_PROPERTIES,
-  pseudoElements: PSEUDO_ELEMENTS
+  properties: db.CSS_PROPERTIES,
+  pseudoElements: db.PSEUDO_ELEMENTS
 };
--- a/devtools/shared/tests/unit/test_css-properties-db.js
+++ b/devtools/shared/tests/unit/test_css-properties-db.js
@@ -1,45 +1,49 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that the devtool's client-side CSS properties database is in sync with the values
- * on the platform. If they are not, then `mach devtools-css-db` needs to be run to
- * make everything up to date. Nightly, aurora, beta, and release may have different
- * preferences for what CSS values are enabled. The static CSS properties database can
- * be slightly different from the target platform as long as there is a preference that
- * exists that turns off that CSS property.
+ * on the platform (in Nightly only). If they are not, then `mach devtools-css-db` needs
+ * to be run to make everything up to date. Nightly, aurora, beta, and release may have
+ * different CSS properties and values. These are based on preferences and compiler flags.
+ *
+ * This test broke uplifts as the database needed to be regenerated every uplift. The
+ * combination of compiler flags and preferences means that it's too difficult to
+ * statically determine which properties are enabled between Firefox releases.
+ *
+ * Because of these difficulties, the database only needs to be up to date with Nightly.
+ * It is a fallback that is only used if the remote debugging protocol doesn't support
+ * providing a CSS database, so it's ok if the provided properties don't exactly match
+ * the inspected target in this particular case.
  */
 
 "use strict";
 
 const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
                            .getService(Components.interfaces.inIDOMUtils);
 
-const {PSEUDO_ELEMENTS, CSS_PROPERTIES, PREFERENCES} = require("devtools/shared/css/generated/properties-db");
+const {PSEUDO_ELEMENTS, CSS_PROPERTIES} = require("devtools/shared/css/generated/properties-db");
 const {generateCssProperties} = require("devtools/server/actors/css-properties");
-const { Preferences } = require("resource://gre/modules/Preferences.jsm");
 
 function run_test() {
   const propertiesErrorMessage = "If this assertion fails, then the client side CSS " +
                                  "properties list in devtools is out of sync with the " +
                                  "CSS properties on the platform. To fix this " +
                                  "assertion run `mach devtools-css-db` to re-generate " +
                                  "the client side properties.";
 
   // Check that the platform and client match for pseudo elements.
   deepEqual(PSEUDO_ELEMENTS, DOMUtils.getCSSPseudoElementNames(), `The pseudo elements ` +
             `match on the client and platform. ${propertiesErrorMessage}`);
 
   /**
    * Check that the platform and client match for the details on their CSS properties.
-   * Enumerate each property to aid in debugging. Sometimes these properties don't
-   * completely agree due to differences in preferences. Check the currently set
-   * preference for that property to see if it's enabled.
+   * Enumerate each property to aid in debugging.
    */
   const platformProperties = generateCssProperties();
 
   for (let propertyName in CSS_PROPERTIES) {
     const platformProperty = platformProperties[propertyName];
     const clientProperty = CSS_PROPERTIES[propertyName];
     const deepEqual = isJsonDeepEqual(platformProperty, clientProperty);
 
@@ -66,24 +70,18 @@ function run_test() {
     // Filter out OS-specific properties.
     .filter(name => name && name.indexOf("-moz-osx-") === -1);
 
   if (mismatches.length === 0) {
     ok(true, "No client and platform CSS property database mismatches were found.");
   }
 
   mismatches.forEach(propertyName => {
-    if (getPreference(propertyName) === false) {
-      ok(true, `The static database and platform do not agree on the property ` +
-               `"${propertyName}" This is ok because it is currently disabled through ` +
-               `a preference.`);
-    } else {
-      ok(false, `The static database and platform do not agree on the property ` +
-                `"${propertyName}" ${propertiesErrorMessage}`);
-    }
+    ok(false, `The static database and platform do not agree on the property ` +
+              `"${propertyName}" ${propertiesErrorMessage}`);
   });
 }
 
 /**
  * Check JSON-serializable objects for deep equality.
  */
 function isJsonDeepEqual(a, b) {
   // Handle primitives.
@@ -115,34 +113,16 @@ function isJsonDeepEqual(a, b) {
     return Object.keys(a).length === Object.keys(b).length;
   }
 
   // Not something handled by these cases, therefore not equal.
   return false;
 }
 
 /**
- * Get the preference value of whether this property is enabled. Returns an empty string
- * if no preference exists.
- *
- * @param {String} propertyName
- * @return {Boolean|undefined}
- */
-function getPreference(propertyName) {
-  const preference = PREFERENCES.find(([prefPropertyName, preferenceKey]) => {
-    return prefPropertyName === propertyName && !!preferenceKey;
-  });
-
-  if (preference) {
-    return Preferences.get(preference[1]);
-  }
-  return undefined;
-}
-
-/**
  * Take the keys of two objects, and return the ones that don't match.
  *
  * @param {Object} a
  * @param {Object} b
  * @return {Array} keys
  */
 function getKeyMismatches(a, b) {
   const aNames = Object.keys(a);
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -54,37 +54,24 @@ if (!BrowserElementIsReady) {
         Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
       }
     } else {
       // rocketbar in system app and other in-process case (ex. B2G desktop client)
       Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
     }
 
     Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
-  } else {
-    if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 0) {
-      if (docShell.asyncPanZoomEnabled === false) {
-        ContentPanningAPZDisabled.init();
-      }
-      ContentPanning.init();
-    }
   }
 
   function onDestroy() {
     removeMessageListener("browser-element-api:destroy", onDestroy);
 
     if (api) {
       api.destroy();
     }
-    if ("ContentPanning" in this) {
-      ContentPanning.destroy();
-    }
-    if ("ContentPanningAPZDisabled" in this) {
-      ContentPanningAPZDisabled.destroy();
-    }
     if ("CopyPasteAssistent" in this) {
       CopyPasteAssistent.destroy();
     }
 
     BrowserElementIsReady = false;
   }
   addMessageListener("browser-element-api:destroy", onDestroy);
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -67,28 +67,30 @@ using namespace mozilla::media;
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 #undef SAMPLE_LOG
 #undef DECODER_WARN
 #undef DUMP_LOG
 #undef SFMT
 #undef SLOG
 #undef SWARN
+#undef SDUMP
 
 #define FMT(x, ...) "Decoder=%p " x, mDecoderID, ##__VA_ARGS__
-#define DECODER_LOG(...) MOZ_LOG(gMediaDecoderLog, LogLevel::Debug,   (FMT(__VA_ARGS__)))
-#define VERBOSE_LOG(...) MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, (FMT(__VA_ARGS__)))
-#define SAMPLE_LOG(...)  MOZ_LOG(gMediaSampleLog,  LogLevel::Debug,   (FMT(__VA_ARGS__)))
-#define DECODER_WARN(...) NS_WARNING(nsPrintfCString(FMT(__VA_ARGS__)).get())
-#define DUMP_LOG(...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(FMT(__VA_ARGS__)).get(), nullptr, nullptr, -1)
+#define DECODER_LOG(x, ...) MOZ_LOG(gMediaDecoderLog, LogLevel::Debug,   (FMT(x, ##__VA_ARGS__)))
+#define VERBOSE_LOG(x, ...) MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, (FMT(x, ##__VA_ARGS__)))
+#define SAMPLE_LOG(x, ...)  MOZ_LOG(gMediaSampleLog,  LogLevel::Debug,   (FMT(x, ##__VA_ARGS__)))
+#define DECODER_WARN(x, ...) NS_WARNING(nsPrintfCString(FMT(x, ##__VA_ARGS__)).get())
+#define DUMP_LOG(x, ...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, nullptr, -1)
 
 // Used by StateObject and its sub-classes
 #define SFMT(x, ...) "Decoder=%p state=%s " x, mMaster->mDecoderID, ToStateStr(GetState()), ##__VA_ARGS__
-#define SLOG(...) MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, (SFMT(__VA_ARGS__)))
-#define SWARN(...) NS_WARNING(nsPrintfCString(SFMT(__VA_ARGS__)).get())
+#define SLOG(x, ...) MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, (SFMT(x, ##__VA_ARGS__)))
+#define SWARN(x, ...) NS_WARNING(nsPrintfCString(SFMT(x, ##__VA_ARGS__)).get())
+#define SDUMP(x, ...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(SFMT(x, ##__VA_ARGS__)).get(), nullptr, nullptr, -1)
 
 // Certain constants get stored as member variables and then adjusted by various
 // scale factors on a per-decoder basis. We want to make sure to avoid using these
 // constants directly, so we put them in a namespace.
 namespace detail {
 
 // If audio queue has less than this many usecs of decoded audio, we won't risk
 // trying to decode the video, we'll skip decoding video up to the next
@@ -216,21 +218,23 @@ public:
 
   virtual bool HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart)
   {
     return false;
   }
 
   virtual bool HandleEndOfStream() { return false; }
 
-  virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget)
-  {
-    MOZ_ASSERT(false, "Can't seek in this state");
-    return nullptr;
-  }
+  virtual bool HandleWaitingForData() { return false; }
+
+  virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) = 0;
+
+  virtual bool HandleAudioCaptured() { return false; }
+
+  virtual void DumpDebugInfo() {}
 
 protected:
   using Master = MediaDecoderStateMachine;
   explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
   TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
   MediaResource* Resource() const { return mMaster->mResource; }
   MediaDecoderReaderWrapper* Reader() const { return mMaster->mReader; }
   const MediaInfo& Info() const { return mMaster->Info(); }
@@ -281,16 +285,22 @@ public:
   }
 
   bool HandleDormant(bool aDormant) override
   {
     mPendingDormant = aDormant;
     return true;
   }
 
+  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
+  {
+    MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek while decoding metadata.");
+    return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
+  }
+
 private:
   void OnMetadataRead(MetadataHolder* aMetadata)
   {
     mMetadataRequest.Complete();
 
     // Set mode to PLAYBACK after reading metadata.
     Resource()->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
 
@@ -556,45 +566,45 @@ public:
 
     if (mMaster->CheckIfDecodeComplete()) {
       SetState(DECODER_STATE_COMPLETED);
       return;
     }
 
     mDecodeStartTime = TimeStamp::Now();
 
-    mMaster->mIsPrerolling = true;
-    mMaster->MaybeStopPrerolling();
+    MaybeStopPrerolling();
 
     // Ensure that we've got tasks enqueued to decode data if we need to.
     mMaster->DispatchDecodeTasksIfNeeded();
 
     mMaster->ScheduleStateMachine();
   }
 
   void Exit() override
   {
     if (!mDecodeStartTime.IsNull()) {
       TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
       SLOG("Exiting DECODING, decoded for %.3lfs", decodeDuration.ToSeconds());
     }
-    mMaster->mIsPrerolling = false;
   }
 
   void Step() override
   {
     if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
         mMaster->IsPlaying()) {
       // We're playing, but the element/decoder is in paused state. Stop
       // playing!
       mMaster->StopPlayback();
     }
 
     // Start playback if necessary so that the clock can be properly queried.
-    mMaster->MaybeStartPlayback();
+    if (!mIsPrerolling) {
+      mMaster->MaybeStartPlayback();
+    }
 
     mMaster->UpdatePlaybackPositionPeriodically();
 
     MOZ_ASSERT(!mMaster->IsPlaying() ||
                mMaster->IsStateMachineScheduled(),
                "Must have timer scheduled");
 
     mMaster->MaybeStartBuffering();
@@ -603,39 +613,68 @@ public:
   State GetState() const override
   {
     return DECODER_STATE_DECODING;
   }
 
   bool HandleAudioDecoded(MediaData* aAudio) override
   {
     mMaster->Push(aAudio, MediaData::AUDIO_DATA);
-    mMaster->MaybeStopPrerolling();
+    MaybeStopPrerolling();
     return true;
   }
 
   bool HandleVideoDecoded(MediaData* aVideo, TimeStamp aDecodeStart) override
   {
     mMaster->Push(aVideo, MediaData::VIDEO_DATA);
-    mMaster->MaybeStopPrerolling();
+    MaybeStopPrerolling();
     CheckSlowDecoding(aDecodeStart);
     return true;
   }
 
   RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
   {
     mMaster->mQueuedSeek.RejectIfExists(__func__);
     SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
     SeekJob seekJob;
     seekJob.mTarget = aTarget;
     RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
     mMaster->InitiateSeek(Move(seekJob));
     return p.forget();
   }
 
+  bool HandleEndOfStream() override
+  {
+    if (mMaster->CheckIfDecodeComplete()) {
+      SetState(DECODER_STATE_COMPLETED);
+    } else {
+      MaybeStopPrerolling();
+    }
+    return true;
+  }
+
+  bool HandleWaitingForData() override
+  {
+    MaybeStopPrerolling();
+    return true;
+  }
+
+  bool HandleAudioCaptured() override
+  {
+    MaybeStopPrerolling();
+    // MediaSink is changed. Schedule Step() to check if we can start playback.
+    mMaster->ScheduleStateMachine();
+    return true;
+  }
+
+  void DumpDebugInfo() override
+  {
+    SDUMP("mIsPrerolling=%d", mIsPrerolling);
+  }
+
 private:
   void CheckSlowDecoding(TimeStamp aDecodeStart)
   {
     // For non async readers, if the requested video sample was slow to
     // arrive, increase the amount of audio we buffer to ensure that we
     // don't run out of audio. This is unnecessary for async readers,
     // since they decode audio and video on different threads so they
     // are unlikely to run out of decoded audio.
@@ -658,26 +697,52 @@ private:
       SLOG("Slow video decode, set "
            "mLowAudioThresholdUsecs=%lld "
            "mAmpleAudioThresholdUsecs=%lld",
            mMaster->mLowAudioThresholdUsecs,
            mMaster->mAmpleAudioThresholdUsecs);
     }
   }
 
-  bool HandleEndOfStream() override
+  bool DonePrerollingAudio()
+  {
+    return !mMaster->IsAudioDecoding() ||
+           mMaster->GetDecodedAudioDuration() >=
+             mMaster->AudioPrerollUsecs() * mMaster->mPlaybackRate;
+  }
+
+  bool DonePrerollingVideo()
   {
-    if (mMaster->CheckIfDecodeComplete()) {
-      SetState(DECODER_STATE_COMPLETED);
+    return !mMaster->IsVideoDecoding() ||
+           static_cast<uint32_t>(mMaster->VideoQueue().GetSize()) >=
+             mMaster->VideoPrerollFrames() * mMaster->mPlaybackRate + 1;
+  }
+
+  void MaybeStopPrerolling()
+  {
+    if (mIsPrerolling &&
+        (DonePrerollingAudio() || Reader()->IsWaitingAudioData()) &&
+        (DonePrerollingVideo() || Reader()->IsWaitingVideoData())) {
+      mIsPrerolling = false;
+      // Check if we can start playback.
+      mMaster->ScheduleStateMachine();
     }
-    return true;
   }
 
   // Time at which we started decoding.
   TimeStamp mDecodeStartTime;
+
+  // When we start decoding (either for the first time, or after a pause)
+  // we may be low on decoded data. We don't want our "low data" logic to
+  // kick in and decide that we're low on decoded data because the download
+  // can't keep up with the decode, and cause us to pause playback. So we
+  // have a "preroll" stage, where we ignore the results of our "low data"
+  // logic during the first few frames of our decode. This occurs during
+  // playback.
+  bool mIsPrerolling = true;
 };
 
 class MediaDecoderStateMachine::SeekingState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit SeekingState(Master* aPtr, SeekJob aSeekJob)
     : StateObject(aPtr), mSeekJob(Move(aSeekJob)) {}
@@ -1104,16 +1169,23 @@ public:
     SLOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
     SeekJob seekJob;
     seekJob.mTarget = aTarget;
     RefPtr<MediaDecoder::SeekPromise> p = seekJob.mPromise.Ensure(__func__);
     mMaster->InitiateSeek(Move(seekJob));
     return p.forget();
   }
 
+  bool HandleAudioCaptured() override
+  {
+    // MediaSink is changed. Schedule Step() to check if we can start playback.
+    mMaster->ScheduleStateMachine();
+    return true;
+  }
+
 private:
   bool mSentPlaybackEndedEvent = false;
 };
 
 class MediaDecoderStateMachine::ShutdownState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
@@ -1133,16 +1205,22 @@ public:
   {
     return DECODER_STATE_SHUTDOWN;
   }
 
   bool HandleDormant(bool aDormant) override
   {
     return true;
   }
+
+  RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override
+  {
+    MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek in shutdown state.");
+    return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
+  }
 };
 
 #define INIT_WATCHABLE(name, val) \
   name(val, "MediaDecoderStateMachine::" #name)
 #define INIT_MIRROR(name, val) \
   name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Mirror)")
 #define INIT_CANONICAL(name, val) \
   name(mTaskQueue, val, "MediaDecoderStateMachine::" #name " (Canonical)")
@@ -1537,17 +1615,17 @@ MediaDecoderStateMachine::OnNotDecoded(M
   }
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
     MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
     mReader->WaitForData(aType);
-    MaybeStopPrerolling();
+    mStateObj->HandleWaitingForData();
     return;
   }
 
   if (aError == NS_ERROR_DOM_MEDIA_CANCELED) {
     if (isAudio) {
       EnsureAudioDecodeTaskQueued();
     } else {
       EnsureVideoDecodeTaskQueued();
@@ -1564,18 +1642,16 @@ MediaDecoderStateMachine::OnNotDecoded(M
   // This is an EOS. Finish off the queue, and then handle things based on our
   // state.
   if (isAudio) {
     AudioQueue().Finish();
   } else {
     VideoQueue().Finish();
   }
 
-  MaybeStopPrerolling();
-
   mStateObj->HandleEndOfStream();
 }
 
 void
 MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideo,
                                          TimeStamp aDecodeStartTime)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1714,47 +1790,34 @@ void MediaDecoderStateMachine::StopPlayb
   if (IsPlaying()) {
     mMediaSink->SetPlaying(false);
     MOZ_ASSERT(!IsPlaying());
   }
 
   DispatchDecodeTasksIfNeeded();
 }
 
-void
-MediaDecoderStateMachine::MaybeStopPrerolling()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  if (mIsPrerolling &&
-      (DonePrerollingAudio() || mReader->IsWaitingAudioData()) &&
-      (DonePrerollingVideo() || mReader->IsWaitingVideoData())) {
-    mIsPrerolling = false;
-    // Check if we can start playback.
-    ScheduleStateMachine();
-  }
-}
-
 void MediaDecoderStateMachine::MaybeStartPlayback()
 {
   MOZ_ASSERT(OnTaskQueue());
   // Should try to start playback only after decoding first frames.
   MOZ_ASSERT(mSentFirstFrameLoadedEvent);
   MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
              mState == DECODER_STATE_COMPLETED);
 
   if (IsPlaying()) {
     // Logging this case is really spammy - don't do it.
     return;
   }
 
   bool playStatePermits = mPlayState == MediaDecoder::PLAY_STATE_PLAYING;
-  if (!playStatePermits || mIsPrerolling || mAudioOffloading) {
+  if (!playStatePermits || mAudioOffloading) {
     DECODER_LOG("Not starting playback [playStatePermits: %d, "
-                "mIsPrerolling: %d, mAudioOffloading: %d]",
-                playStatePermits, mIsPrerolling, mAudioOffloading);
+                "mAudioOffloading: %d]",
+                playStatePermits, mAudioOffloading);
     return;
   }
 
   DECODER_LOG("MaybeStartPlayback() starting playback");
   mOnPlaybackEvent.Notify(MediaEventType::PlaybackStarted);
   StartMediaSink();
 
   if (!IsPlaying()) {
@@ -2982,30 +3045,25 @@ MediaDecoderStateMachine::SetAudioCaptur
   mMediaSink->Shutdown();
 
   // Create a new sink according to whether audio is captured.
   mMediaSink = CreateMediaSink(aCaptured);
 
   // Restore playback parameters.
   mMediaSink->SetPlaybackParams(params);
 
-  // We don't need to call StartMediaSink() here because IsPlaying() is now
-  // always in sync with the playing state of MediaSink. It will be started in
-  // MaybeStartPlayback() in the next cycle if necessary.
-
   mAudioCaptured = aCaptured;
-  ScheduleStateMachine();
 
   // Don't buffer as much when audio is captured because we don't need to worry
   // about high latency audio devices.
   mAmpleAudioThresholdUsecs = mAudioCaptured ?
                               detail::AMPLE_AUDIO_USECS / 2 :
                               detail::AMPLE_AUDIO_USECS;
 
-  MaybeStopPrerolling();
+  mStateObj->HandleAudioCaptured();
 }
 
 uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const
 {
   MOZ_ASSERT(OnTaskQueue());
   return (mReader->IsAsync() && mReader->VideoIsHardwareAccelerated())
     ? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
     : std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
@@ -3015,25 +3073,26 @@ void
 MediaDecoderStateMachine::DumpDebugInfo()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // It is fine to capture a raw pointer here because MediaDecoder only call
   // this function before shutdown begins.
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
     mMediaSink->DumpDebugInfo();
+    mStateObj->DumpDebugInfo();
     DUMP_LOG(
       "GetMediaTime=%lld GetClock=%lld mMediaSink=%p "
       "mState=%s mPlayState=%d mSentFirstFrameLoadedEvent=%d IsPlaying=%d "
       "mAudioStatus=%s mVideoStatus=%s mDecodedAudioEndTime=%lld mDecodedVideoEndTime=%lld "
-      "mIsPrerolling=%d mAudioCompleted=%d mVideoCompleted=%d",
+      "mAudioCompleted=%d mVideoCompleted=%d",
       GetMediaTime(), mMediaSink->IsStarted() ? GetClock() : -1, mMediaSink.get(),
       ToStateStr(), mPlayState.Ref(), mSentFirstFrameLoadedEvent, IsPlaying(),
       AudioRequestStatus(), VideoRequestStatus(), mDecodedAudioEndTime, mDecodedVideoEndTime,
-      mIsPrerolling, mAudioCompleted.Ref(), mVideoCompleted.Ref());
+      mAudioCompleted.Ref(), mVideoCompleted.Ref());
   });
 
   OwnerThread()->DispatchStateChange(r.forget());
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
                                                bool aFinishWhenEnded)
 {
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -672,42 +672,16 @@ private:
   }
 
   uint32_t VideoPrerollFrames() const
   {
     MOZ_ASSERT(OnTaskQueue());
     return GetAmpleVideoFrames() / 2;
   }
 
-  bool DonePrerollingAudio()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return !IsAudioDecoding() ||
-        GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate;
-  }
-
-  bool DonePrerollingVideo()
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return !IsVideoDecoding() ||
-        static_cast<uint32_t>(VideoQueue().GetSize()) >=
-            VideoPrerollFrames() * mPlaybackRate + 1;
-  }
-
-  void MaybeStopPrerolling();
-
-  // When we start decoding (either for the first time, or after a pause)
-  // we may be low on decoded data. We don't want our "low data" logic to
-  // kick in and decide that we're low on decoded data because the download
-  // can't keep up with the decode, and cause us to pause playback. So we
-  // have a "preroll" stage, where we ignore the results of our "low data"
-  // logic during the first few frames of our decode. This occurs during
-  // playback.
-  bool mIsPrerolling = false;
-
   // Only one of a given pair of ({Audio,Video}DataPromise, WaitForDataPromise)
   // should exist at any given moment.
 
   MediaEventListener mAudioCallback;
   MediaEventListener mVideoCallback;
   MediaEventListener mAudioWaitCallback;
   MediaEventListener mVideoWaitCallback;
 
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1508,28 +1508,23 @@ public:
         }
         if (mAudioDevice) {
           mAudioDevice->Deallocate();
         }
       }
     }
     if (errorMsg) {
       LOG(("%s %d", errorMsg, rv));
-      switch (rv) {
-        case NS_ERROR_NOT_AVAILABLE: {
-          MOZ_ASSERT(badConstraint);
-          Fail(NS_LITERAL_STRING("OverconstrainedError"),
-               NS_LITERAL_STRING(""),
-               NS_ConvertUTF8toUTF16(badConstraint));
-          break;
-        }
-        default:
-          Fail(NS_LITERAL_STRING("NotReadableError"),
-               NS_ConvertUTF8toUTF16(errorMsg));
-          break;
+      if (badConstraint) {
+        Fail(NS_LITERAL_STRING("OverconstrainedError"),
+             NS_LITERAL_STRING(""),
+             NS_ConvertUTF8toUTF16(badConstraint));
+      } else {
+        Fail(NS_LITERAL_STRING("NotReadableError"),
+             NS_ConvertUTF8toUTF16(errorMsg));
       }
       return NS_OK;
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
@@ -3515,17 +3510,17 @@ GetUserMediaCallbackMediaStreamListener:
       }
       RefPtr<PledgeVoid> p = mgr->mOutstandingVoidPledges.Remove(id);
       if (p) {
         if (NS_SUCCEEDED(rv)) {
           p->Resolve(false);
         } else {
           auto* window = nsGlobalWindow::GetInnerWindowWithId(windowId);
           if (window) {
-            if (rv == NS_ERROR_NOT_AVAILABLE) {
+            if (badConstraint) {
               nsString constraint;
               constraint.AssignASCII(badConstraint);
               RefPtr<MediaStreamError> error =
                   new MediaStreamError(window->AsInner(),
                                        NS_LITERAL_STRING("OverconstrainedError"),
                                        NS_LITERAL_STRING(""),
                                        constraint);
               p->Reject(error);
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -11,19 +11,19 @@
 namespace mozilla {
 
 extern LazyLogModule gMediaDecoderLog;
 
 #undef FMT
 #undef DUMP_LOG
 
 #define FMT(x, ...) "VideoSink=%p " x, this, ##__VA_ARGS__
-#define VSINK_LOG(...)   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug,   (FMT(__VA_ARGS__)))
-#define VSINK_LOG_V(...) MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, (FMT(__VA_ARGS__)))
-#define DUMP_LOG(...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(FMT(__VA_ARGS__)).get(), nullptr, nullptr, -1)
+#define VSINK_LOG(x, ...)   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug,   (FMT(x, ##__VA_ARGS__)))
+#define VSINK_LOG_V(x, ...) MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, (FMT(x, ##__VA_ARGS__)))
+#define DUMP_LOG(x, ...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, nullptr, -1)
 
 using namespace mozilla::layers;
 
 namespace media {
 
 VideoSink::VideoSink(AbstractThread* aThread,
                      MediaSink* aAudioSink,
                      MediaQueue<MediaData>& aVideoQueue,
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -61,17 +61,18 @@ AudioStreamAnalyser.prototype = {
   },
 
   /**
    * Append a canvas to the DOM where the frequency data are drawn.
    * Useful to debug tests.
    */
   enableDebugCanvas: function() {
     var cvs = this.debugCanvas = document.createElement("canvas");
-    document.getElementById("content").appendChild(cvs);
+    const content = document.getElementById("content");
+    content.insertBefore(cvs, content.children[0]);
 
     // Easy: 1px per bin
     cvs.width = this.analyser.frequencyBinCount;
     cvs.height = 128;
     cvs.style.border = "1px solid red";
 
     var c = cvs.getContext('2d');
     c.fillStyle = 'black';
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2557,17 +2557,17 @@ WorkerPrivateParent<Derived>::Freeze(nsP
   AssertIsOnParentThread();
 
   // Shared workers are only frozen if all of their owning documents are
   // frozen. It can happen that mSharedWorkers is empty but this thread has
   // not been unregistered yet.
   if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) {
     AssertIsOnMainThread();
 
-    bool allFrozen = false;
+    bool allFrozen = true;
 
     for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) {
       if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) {
         // Calling Freeze() may change the refcount, ensure that the worker
         // outlives this call.
         RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i];
 
         kungFuDeathGrip->Freeze();
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -592,17 +592,17 @@ js::AreGCGrayBitsValid(JSContext* cx)
 {
     return cx->gc.areGrayBitsValid();
 }
 
 JS_FRIEND_API(bool)
 js::ZoneGlobalsAreAllGray(JS::Zone* zone)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
-        JSObject* obj = comp->maybeGlobal();
+        JSObject* obj = comp->unsafeUnbarrieredMaybeGlobal();
         if (!obj || !JS::ObjectIsMarkedGray(obj))
             return false;
     }
     return true;
 }
 
 namespace {
 struct VisitGrayCallbackFunctor {
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -2776,21 +2776,25 @@ CrossAxisPositionTracker::
   // 'normal' behaves as 'stretch'
   if (mAlignContent == NS_STYLE_ALIGN_NORMAL) {
     mAlignContent = NS_STYLE_ALIGN_STRETCH;
   }
 
   // XXX strip of the <overflow-position> bit until we implement that
   mAlignContent &= ~NS_STYLE_ALIGN_FLAG_BITS;
 
-  if (!aFirstLine->getNext()) {
+  const bool isSingleLine =
+    NS_STYLE_FLEX_WRAP_NOWRAP == aReflowInput.mStylePosition->mFlexWrap;
+  if (isSingleLine) {
+    MOZ_ASSERT(!aFirstLine->getNext(),
+               "If we're styled as single-line, we should only have 1 line");
     // "If the flex container is single-line and has a definite cross size, the
     // cross size of the flex line is the flex container's inner cross size."
     //
-    // SOURCE: http://dev.w3.org/csswg/css-flexbox/#algo-line-break
+    // SOURCE: https://drafts.csswg.org/css-flexbox/#algo-cross-line
     // NOTE: This means (by definition) that there's no packing space, which
     // means we don't need to be concerned with "align-conent" at all and we
     // can return early. This is handy, because this is the usual case (for
     // single-line flexbox).
     if (aIsCrossSizeDefinite) {
       aFirstLine->SetLineCrossSize(aContentBoxCrossSize);
       return;
     }
--- a/layout/reftests/font-face/src-list-local-full-quotes.html
+++ b/layout/reftests/font-face/src-list-local-full-quotes.html
@@ -10,28 +10,28 @@ body {
   margin: 50px;
   font-size: 24pt;
 }
 
 /* use full names */
 
 @font-face {
   font-family: test-regular;
-  src: local("Helvetica Neue"), local("Bitstream Vera Sans"), local("Bitstream Vera Sans Roman"), local("FreeSans"), local("Free Sans"), local("SwissA"), local("DejaVu Sans"), local("Arial");
+  src: local("Helvetica Neue"), local("Bitstream Vera Sans"), local("Bitstream Vera Sans Roman"), local("DejaVu Sans"), local("FreeSans"), local("Free Sans"), local("SwissA"), local("Arial");
 }
 
 /* use Helvetica on the Mac, since Futura has no bold face on 10.4, 10.5 */
 @font-face {
   font-family: test-bold;
-  src: local("Helvetica Neue Bold"), local("Bitstream Vera Sans Bold"), local("FreeSans Bold"), local("Free Sans Bold"), local("SwissA Bold"), local("DejaVu Sans Bold"), local("Arial Bold");
+  src: local("Helvetica Neue Bold"), local("Bitstream Vera Sans Bold"), local("DejaVu Sans Bold"), local("FreeSans Bold"), local("Free Sans Bold"), local("SwissA Bold"), local("Arial Bold");
 }
 
 @font-face {
   font-family: test-italic;
-  src: local("Helvetica Neue Italic"), local("Bitstream Vera Sans Oblique"), local("FreeSans Oblique"), local("Free Sans Oblique"), local("SwissA Italic"), local("DejaVu Sans Oblique"), local("Arial Italic");
+  src: local("Helvetica Neue Italic"), local("Bitstream Vera Sans Oblique"), local("DejaVu Sans Oblique"), local("FreeSans Oblique"), local("Free Sans Oblique"), local("SwissA Italic"), local("Arial Italic");
 }
 
 .regular { font-family: test-regular, serif; }
 .bold { font-family: test-bold, serif; }
 .italic { font-family: test-italic, serif; }
 
 </style>
 
--- a/layout/reftests/font-face/src-list-local-full-ref.html
+++ b/layout/reftests/font-face/src-list-local-full-ref.html
@@ -6,19 +6,20 @@
 
 <style type="text/css">
 
 body {
   margin: 50px;
   font-size: 24pt;
 }
 
-/* use full names */
+/* Bitstream Vera must be adjacent to DejaVu to because the latter is picked
+   up by fontconfig aliases for the former. */
 
-p { font-family: Helvetica Neue, Bitstream Vera Sans, FreeSans, SwissA, DejaVu Sans, Arial, serif; }
+p { font-family: Helvetica Neue, Bitstream Vera Sans, DejaVu Sans, FreeSans, SwissA, Arial, serif; }
 
 .regular {  }
 .bold { font-weight: bold; }
 .italic { font-style: italic; }
 
 </style>
   
 <script type="text/javascript">
--- a/layout/reftests/font-face/src-list-local-full.html
+++ b/layout/reftests/font-face/src-list-local-full.html
@@ -10,28 +10,28 @@ body {
   margin: 50px;
   font-size: 24pt;
 }
 
 /* use full names */
 
 @font-face {
   font-family: test-regular;
-  src: local(Helvetica Neue), local(Bitstream Vera Sans), local(Bitstream Vera Sans Roman), local(FreeSans), local(Free Sans), local(SwissA), local(DejaVu Sans), local(Arial);
+  src: local(Helvetica Neue), local(Bitstream Vera Sans), local(Bitstream Vera Sans Roman), local(DejaVu Sans), local(FreeSans), local(Free Sans), local(SwissA), local(Arial);
 }
 
 /* use Helvetica on the Mac, since Futura has no bold face on 10.4, 10.5 */
 @font-face {
   font-family: test-bold;
-  src: local(Helvetica Neue Bold), local(Bitstream Vera Sans Bold), local(FreeSans Bold), local(Free Sans Bold), local(SwissA Bold), local(DejaVu Sans Bold), local(Arial Bold);
+  src: local(Helvetica Neue Bold), local(Bitstream Vera Sans Bold), local(DejaVu Sans Bold), local(FreeSans Bold), local(Free Sans Bold), local(SwissA Bold), local(Arial Bold);
 }
 
 @font-face {
   font-family: test-italic;
-  src: local(Helvetica Neue Italic), local(Bitstream Vera Sans Oblique), local(FreeSans Oblique), local(Free Sans Oblique), local(SwissA Italic), local(DejaVu Sans Oblique), local(Arial Italic);
+  src: local(Helvetica Neue Italic), local(Bitstream Vera Sans Oblique), local(DejaVu Sans Oblique), local(FreeSans Oblique), local(Free Sans Oblique), local(SwissA Italic), local(Arial Italic);
 }
 
 .regular { font-family: test-regular, serif; }
 .bold { font-family: test-bold, serif; }
 .italic { font-family: test-italic, serif; }
 
 </style>
 
--- a/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-content-horiz-001-ref.xhtml
+++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-content-horiz-001-ref.xhtml
@@ -66,31 +66,31 @@
     <div class="flexbox">
       <div class="a"/>
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
 
     <!-- flex-end -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-top: 190px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-top: 160px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-top: 120px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
 
     <!-- center -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-top: 95px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-top: 80px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-top: 60px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
@@ -108,17 +108,17 @@
     <div class="flexbox">
       <div class="a"/>
       <div class="b" style="margin-top: 60px"><div class="fixedSizeChild"/></div>
       <div class="c" style="margin-top: 60px"/>
     </div>
 
     <!-- space-around -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-top: 95px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-top: 40px"/>
       <div class="b" style="margin-top: 80px"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-top: 20px"/>
       <div class="b" style="margin-top: 40px"><div class="fixedSizeChild"/></div>
--- a/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-content-vert-001-ref.xhtml
+++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-align-content-vert-001-ref.xhtml
@@ -69,31 +69,31 @@
     <div class="flexbox">
       <div class="a"/>
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
 
     <!-- flex-end -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-left: 190px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 160px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 120px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
 
     <!-- center -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-left: 95px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 80px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 60px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
@@ -111,17 +111,17 @@
     <div class="flexbox">
       <div class="a"/>
       <div class="b" style="margin-left: 60px"><div class="fixedSizeChild"/></div>
       <div class="c" style="margin-left: 60px"/>
     </div>
 
     <!-- space-around -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-left: 95px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 40px"/>
       <div class="b" style="margin-left: 80px"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 20px"/>
       <div class="b" style="margin-left: 40px"><div class="fixedSizeChild"/></div>
@@ -139,17 +139,17 @@
     <div class="flexbox">
       <div class="a"/>
       <div class="b"><div class="fixedSizeChild"/></div>
       <div class="c"/>
     </div>
 
     <!-- right -->
     <div class="flexbox">
-      <div class="a"/>
+      <div class="a" style="margin-left: 190px"/>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 160px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
     </div>
     <div class="flexbox">
       <div class="a" style="margin-left: 120px"/>
       <div class="b"><div class="fixedSizeChild"/></div>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1450,23 +1450,21 @@ pref("network.http.accept.default", "tex
 
 // Prefs allowing granular control of referers
 // 0=don't send any, 1=send only on clicks, 2=send on image requests as well
 pref("network.http.sendRefererHeader",      2);
 // false=real referer, true=spoof referer (use target URI as referer)
 pref("network.http.referer.spoofSource", false);
 // 0=full URI, 1=scheme+host+port+path, 2=scheme+host+port
 pref("network.http.referer.trimmingPolicy", 0);
+// 0=full URI, 1=scheme+host+port+path, 2=scheme+host+port
+pref("network.http.referer.XOriginTrimmingPolicy", 0);
 // 0=always send, 1=send iff base domains match, 2=send iff hosts match
 pref("network.http.referer.XOriginPolicy", 0);
 
-// Controls whether we send HTTPS referres to other HTTPS sites.
-// By default this is enabled for compatibility (see bug 141641)
-pref("network.http.sendSecureXSiteReferrer", true);
-
 // Controls whether referrer attributes in <a>, <img>, <area>, <iframe>, and <link> are honoured
 pref("network.http.enablePerElementReferrer", true);
 
 // Maximum number of consecutive redirects before aborting.
 pref("network.http.redirection-limit", 20);
 
 // Enable http compression: comment this out in case of problems with 1.1
 // NOTE: support for "compress" has been disabled per bug 196406.
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -1403,32 +1403,16 @@ HttpBaseChannel::SetReferrerWithPolicy(n
     // It's ok to send referrer for https-to-http scenarios if the referrer
     // policy is "unsafe-url", "origin", or "origin-when-cross-origin".
     if (referrerPolicy != REFERRER_POLICY_UNSAFE_URL &&
 	referrerPolicy != REFERRER_POLICY_ORIGIN_WHEN_XORIGIN &&
         referrerPolicy != REFERRER_POLICY_ORIGIN) {
 
       // in other referrer policies, https->http is not allowed...
       if (!match) return NS_OK;
-
-      // ...and https->https is possibly only allowed if the hosts match.
-      if (!gHttpHandler->SendSecureXSiteReferrer()) {
-        nsAutoCString referrerHost;
-        nsAutoCString host;
-
-        rv = referrer->GetAsciiHost(referrerHost);
-        if (NS_FAILED(rv)) return rv;
-
-        rv = mURI->GetAsciiHost(host);
-        if (NS_FAILED(rv)) return rv;
-
-        // GetAsciiHost returns lowercase hostname.
-        if (!referrerHost.Equals(host))
-          return NS_OK;
-      }
     }
   }
 
   // for cross-origin-based referrer changes (not just host-based), figure out
   // if the referrer is being sent cross-origin.
   nsCOMPtr<nsIURI> triggeringURI;
   bool isCrossOrigin = true;
   if (mLoadInfo) {
@@ -1512,16 +1496,25 @@ HttpBaseChannel::SetReferrerWithPolicy(n
 
   // strip away any userpass; we don't want to be giving out passwords ;-)
   // This is required by Referrer Policy stripping algorithm.
   rv = clone->SetUserPass(EmptyCString());
   if (NS_FAILED(rv)) return rv;
 
   nsAutoCString spec;
 
+  // Apply the user cross-origin trimming policy if it's more
+  // restrictive than the general one.
+  if (isCrossOrigin) {
+    int userReferrerXOriginTrimmingPolicy =
+      gHttpHandler->ReferrerXOriginTrimmingPolicy();
+    userReferrerTrimmingPolicy =
+      std::max(userReferrerTrimmingPolicy, userReferrerXOriginTrimmingPolicy);
+  }
+
   // site-specified referrer trimming may affect the trim level
   // "unsafe-url" behaves like "origin" (send referrer in the same situations) but
   // "unsafe-url" sends the whole referrer and origin removes the path.
   // "origin-when-cross-origin" trims the referrer only when the request is
   // cross-origin.
   // "Strict" request from https->http case was bailed out, so here:
   // "strict-origin" behaves the same as "origin".
   // "strict-origin-when-cross-origin" behaves the same as "origin-when-cross-origin"
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -161,16 +161,17 @@ nsHttpHandler *gHttpHandler = nullptr;
 
 nsHttpHandler::nsHttpHandler()
     : mHttpVersion(NS_HTTP_VERSION_1_1)
     , mProxyHttpVersion(NS_HTTP_VERSION_1_1)
     , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE)
     , mReferrerLevel(0xff) // by default we always send a referrer
     , mSpoofReferrerSource(false)
     , mReferrerTrimmingPolicy(0)
+    , mReferrerXOriginTrimmingPolicy(0)
     , mReferrerXOriginPolicy(0)
     , mFastFallbackToIPv4(false)
     , mProxyPipelining(true)
     , mIdleTimeout(PR_SecondsToInterval(10))
     , mSpdyTimeout(PR_SecondsToInterval(180))
     , mResponseTimeout(PR_SecondsToInterval(300))
     , mResponseTimeoutEnabled(false)
     , mNetworkChangedTimeout(5000)
@@ -197,17 +198,16 @@ nsHttpHandler::nsHttpHandler()
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
     , mLegacyAppName("Mozilla")
     , mLegacyAppVersion("5.0")
     , mProduct("Gecko")
     , mCompatFirefoxEnabled(false)
     , mUserAgentIsDirty(true)
     , mPromptTempRedirect(true)
-    , mSendSecureXSiteReferrer(true)
     , mEnablePersistentHttpsCaching(false)
     , mDoNotTrackEnabled(false)
     , mSafeHintEnabled(false)
     , mParentalControlEnabled(false)
     , mHandlerActive(false)
     , mTelemetryEnabled(false)
     , mAllowExperiments(true)
     , mDebugObservations(false)
@@ -1080,16 +1080,22 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     }
 
     if (PREF_CHANGED(HTTP_PREF("referer.trimmingPolicy"))) {
         rv = prefs->GetIntPref(HTTP_PREF("referer.trimmingPolicy"), &val);
         if (NS_SUCCEEDED(rv))
             mReferrerTrimmingPolicy = (uint8_t) clamped(val, 0, 2);
     }
 
+    if (PREF_CHANGED(HTTP_PREF("referer.XOriginTrimmingPolicy"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginTrimmingPolicy"), &val);
+        if (NS_SUCCEEDED(rv))
+            mReferrerXOriginTrimmingPolicy = (uint8_t) clamped(val, 0, 2);
+    }
+
     if (PREF_CHANGED(HTTP_PREF("referer.XOriginPolicy"))) {
         rv = prefs->GetIntPref(HTTP_PREF("referer.XOriginPolicy"), &val);
         if (NS_SUCCEEDED(rv))
             mReferrerXOriginPolicy = (uint8_t) clamped(val, 0, 0xff);
     }
 
     if (PREF_CHANGED(HTTP_PREF("redirection-limit"))) {
         rv = prefs->GetIntPref(HTTP_PREF("redirection-limit"), &val);
@@ -1225,22 +1231,16 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     }
 
     if (PREF_CHANGED(HTTP_PREF("qos"))) {
         rv = prefs->GetIntPref(HTTP_PREF("qos"), &val);
         if (NS_SUCCEEDED(rv))
             mQoSBits = (uint8_t) clamped(val, 0, 0xff);
     }
 
-    if (PREF_CHANGED(HTTP_PREF("sendSecureXSiteReferrer"))) {
-        rv = prefs->GetBoolPref(HTTP_PREF("sendSecureXSiteReferrer"), &cVar);
-        if (NS_SUCCEEDED(rv))
-            mSendSecureXSiteReferrer = cVar;
-    }
-
     if (PREF_CHANGED(HTTP_PREF("accept.default"))) {
         nsXPIDLCString accept;
         rv = prefs->GetCharPref(HTTP_PREF("accept.default"),
                                   getter_Copies(accept));
         if (NS_SUCCEEDED(rv))
             SetAccept(accept);
     }
 
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -77,18 +77,20 @@ public:
 
     const nsAFlatCString &UserAgent();
 
     nsHttpVersion  HttpVersion()             { return mHttpVersion; }
     nsHttpVersion  ProxyHttpVersion()        { return mProxyHttpVersion; }
     uint8_t        ReferrerLevel()           { return mReferrerLevel; }
     bool           SpoofReferrerSource()     { return mSpoofReferrerSource; }
     uint8_t        ReferrerTrimmingPolicy()  { return mReferrerTrimmingPolicy; }
+    uint8_t        ReferrerXOriginTrimmingPolicy() {
+        return mReferrerXOriginTrimmingPolicy;
+    }
     uint8_t        ReferrerXOriginPolicy()   { return mReferrerXOriginPolicy; }
-    bool           SendSecureXSiteReferrer() { return mSendSecureXSiteReferrer; }
     bool           PackagedAppsEnabled()     { return mPackagedAppsEnabled; }
     uint8_t        RedirectionLimit()        { return mRedirectionLimit; }
     PRIntervalTime IdleTimeout()             { return mIdleTimeout; }
     PRIntervalTime SpdyTimeout()             { return mSpdyTimeout; }
     PRIntervalTime ResponseTimeout() {
       return mResponseTimeoutEnabled ? mResponseTimeout : 0;
     }
     PRIntervalTime ResponseTimeoutEnabled()  { return mResponseTimeoutEnabled; }
@@ -412,16 +414,17 @@ private:
     //
 
     uint8_t  mHttpVersion;
     uint8_t  mProxyHttpVersion;
     uint32_t mCapabilities;
     uint8_t  mReferrerLevel;
     uint8_t  mSpoofReferrerSource;
     uint8_t  mReferrerTrimmingPolicy;
+    uint8_t  mReferrerXOriginTrimmingPolicy;
     uint8_t  mReferrerXOriginPolicy;
 
     bool mFastFallbackToIPv4;
     bool mProxyPipelining;
     PRIntervalTime mIdleTimeout;
     PRIntervalTime mSpdyTimeout;
     PRIntervalTime mResponseTimeout;
     bool mResponseTimeoutEnabled;
@@ -487,19 +490,16 @@ private:
     nsCString      mDeviceModelId;
 
     nsCString      mUserAgent;
     nsXPIDLCString mUserAgentOverride;
     bool           mUserAgentIsDirty; // true if mUserAgent should be rebuilt
 
 
     bool           mPromptTempRedirect;
-    // mSendSecureXSiteReferrer: default is false,
-    // if true allow referrer headers between secure non-matching hosts
-    bool           mSendSecureXSiteReferrer;
 
     // Persistent HTTPS caching flag
     bool           mEnablePersistentHttpsCaching;
 
     // For broadcasting tracking preference
     bool           mDoNotTrackEnabled;
 
     // for broadcasting safe hint;
--- a/netwerk/test/unit/test_referrer.js
+++ b/netwerk/test/unit/test_referrer.js
@@ -1,19 +1,24 @@
 Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 function getTestReferrer(server_uri, referer_uri) {
   var uri = NetUtil.newURI(server_uri, "", null)
+  let referrer = NetUtil.newURI(referer_uri, null, null);
+  let triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(referrer, {});
   var chan = NetUtil.newChannel({
     uri: uri,
-    loadUsingSystemPrincipal: true
+    loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+    triggeringPrincipal: triggeringPrincipal,
+    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
   });
 
   chan.QueryInterface(Components.interfaces.nsIHttpChannel);
-  chan.referrer = NetUtil.newURI(referer_uri, null, null);
+  chan.referrer = referrer;
   var header = null;
   try {
     header = chan.getRequestHeader("Referer");
   }
   catch (NS_ERROR_NOT_AVAILABLE) {}
   return header;
 }
 
@@ -26,16 +31,17 @@ function run_test() {
   var referer_uri = "http://foo.example.com/path";
   var referer_uri_2 = "http://bar.examplesite.com/path3?q=blah";
   var referer_uri_2_anchor = "http://bar.examplesite.com/path3?q=blah#anchor";
   var referer_uri_idn = "http://sub1.\xe4lt.example/path";
 
   // for https tests
   var server_uri_https = "https://bar.example.com/anotherpath";
   var referer_uri_https = "https://bar.example.com/path3?q=blah";
+  var referer_uri_2_https = "https://bar.examplesite.com/path3?q=blah";
 
   // tests for sendRefererHeader
   prefs.setIntPref("network.http.sendRefererHeader", 0);
   do_check_null(getTestReferrer(server_uri, referer_uri));
   prefs.setIntPref("network.http.sendRefererHeader", 2);
   do_check_eq(getTestReferrer(server_uri, referer_uri), referer_uri);
 
   // test that https ref is not sent to http
@@ -67,16 +73,36 @@ function run_test() {
   do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/");
   do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/");
   // https test
   do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), "https://bar.example.com/");
   prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
   // test that anchor is lopped off in ordinary case
   do_check_eq(getTestReferrer(server_uri, referer_uri_2_anchor), referer_uri_2);
 
+  // tests for referer.XOriginTrimmingPolicy
+  prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 1);
+  do_check_eq(getTestReferrer(server_uri, referer_uri), "http://foo.example.com/path");
+  do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/path");
+  do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3?q=blah");
+  prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
+  do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+  prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 2);
+  do_check_eq(getTestReferrer(server_uri, referer_uri), "http://foo.example.com/");
+  do_check_eq(getTestReferrer(server_uri, referer_uri_idn), "http://sub1.xn--lt-uia.example/");
+  do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3");
+  prefs.setIntPref("network.http.referer.trimmingPolicy", 0);
+  do_check_eq(getTestReferrer(server_uri, referer_uri_2), "http://bar.examplesite.com/path3?q=blah");
+  // https tests
+  do_check_eq(getTestReferrer(server_uri_https, referer_uri_https), "https://bar.example.com/path3?q=blah");
+  do_check_eq(getTestReferrer(server_uri_https, referer_uri_2_https), "https://bar.examplesite.com/");
+  prefs.setIntPref("network.http.referer.XOriginTrimmingPolicy", 0);
+  // test that anchor is lopped off in ordinary case
+  do_check_eq(getTestReferrer(server_uri, referer_uri_2_anchor), referer_uri_2);
+
   // combination test: send spoofed path-only when hosts match
   var combo_referer_uri = "http://blah.foo.com/path?q=hot";
   var dest_uri = "http://blah.foo.com:9999/spoofedpath?q=bad";
   prefs.setIntPref("network.http.referer.trimmingPolicy", 1);
   prefs.setBoolPref("network.http.referer.spoofSource", true);
   prefs.setIntPref("network.http.referer.XOriginPolicy", 2);
   do_check_eq(getTestReferrer(dest_uri, combo_referer_uri), "http://blah.foo.com:9999/spoofedpath");
   do_check_null(getTestReferrer(dest_uri, "http://gah.foo.com/anotherpath"));
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -18,20 +18,16 @@ const nsDialogParamBlock = "@mozilla.org
 
 const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";
 
 var { NetUtil } = Components.utils.import("resource://gre/modules/NetUtil.jsm", {});
 var { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});
 
 var key;
 
-/**
- * List of certs currently selected in the active tab.
- * @type nsIX509Cert[]
- */
 var selected_certs = [];
 var selected_tree_items = [];
 var selected_index = [];
 var certdb;
 
 var caTreeView;
 var serverTreeView;
 var emailTreeView;
@@ -328,18 +324,18 @@ function backupAllCerts()
   backupCerts();
 }
 
 function editCerts()
 {
   getSelectedCerts();
 
   for (let cert of selected_certs) {
-    window.openDialog("chrome://pippki/content/editcacert.xul", "",
-                      "chrome,centerscreen,modal", cert);
+    window.openDialog("chrome://pippki/content/editcacert.xul", cert.dbKey,
+                      "chrome,centerscreen,modal");
   }
 }
 
 function restoreCerts()
 {
   var bundle = document.getElementById("pippki_bundle");
   var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
   fp.init(window,
--- a/security/manager/pki/resources/content/editcacert.xul
+++ b/security/manager/pki/resources/content/editcacert.xul
@@ -2,29 +2,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/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd">
 
-<dialog id="editCaCert"
+<dialog id="editCaCert" 
         title="&certmgr.editcacert.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         buttons="accept,cancel"
-        ondialogaccept="return onDialogAccept();"
-        onload="onLoad();"
+        ondialogaccept="return doOK();"
+        onload="setWindowName();"
 >
 
   <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
 
   <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
-  <script type="application/javascript"
-          src="chrome://pippki/content/editcacert.js"/>
+  <script type="application/javascript" src="chrome://pippki/content/editcerts.js"/>
 
   <description id="certmsg"/>
   <separator/>
   <description>&certmgr.editcert.edittrust;</description>
   <vbox align="start">
     <checkbox label="&certmgr.editcert.trustssl;"
               id="trustSSL"/>
     <checkbox label="&certmgr.editcert.trustemail;"
rename from security/manager/pki/resources/content/editcacert.js
rename to security/manager/pki/resources/content/editcerts.js
--- a/security/manager/pki/resources/content/editcacert.js
+++ b/security/manager/pki/resources/content/editcerts.js
@@ -1,58 +1,71 @@
 /* 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/. */
 /* import-globals-from pippki.js */
 "use strict";
 
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
-                .getService(Ci.nsIX509CertDB);
-/**
- * Cert to edit the trust of.
- * @type nsIX509Cert
- */
-var gCert;
-
-/**
- * onload() handler.
- */
-function onLoad() {
-  gCert = window.arguments[0];
+const nsIX509Cert = Components.interfaces.nsIX509Cert;
+const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+const nsIX509CertDB = Components.interfaces.nsIX509CertDB;
 
-  let bundle = document.getElementById("pippki_bundle");
-  setText("certmsg",
-          bundle.getFormattedString("editTrustCA", [gCert.commonName]));
-
-  let sslCheckbox = document.getElementById("trustSSL");
-  sslCheckbox.checked = gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                              Ci.nsIX509CertDB.TRUSTED_SSL);
+var certdb;
+var cert;
 
-  let emailCheckbox = document.getElementById("trustEmail");
-  emailCheckbox.checked = gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                                Ci.nsIX509CertDB.TRUSTED_EMAIL);
-
-  let objSignCheckbox = document.getElementById("trustObjSign");
-  objSignCheckbox.checked =
-    gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                          Ci.nsIX509CertDB.TRUSTED_OBJSIGN);
+function doPrompt(msg)
+{
+  let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
+    getService(Components.interfaces.nsIPromptService);
+  prompts.alert(window, null, msg);
 }
 
-/**
- * ondialogaccept() handler.
- *
- * @returns {Boolean} true to make the dialog close, false otherwise.
- */
-function onDialogAccept() {
-  let sslCheckbox = document.getElementById("trustSSL");
-  let emailCheckbox = document.getElementById("trustEmail");
-  let objSignCheckbox = document.getElementById("trustObjSign");
-  let trustSSL = sslCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_SSL : 0;
-  let trustEmail = emailCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_EMAIL : 0;
-  let trustObjSign = objSignCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_OBJSIGN
-                                             : 0;
+function setWindowName()
+{
+  var dbkey = self.name;
+
+  //  Get the cert from the cert database
+  certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
+  cert = certdb.findCertByDBKey(dbkey);
+
+  var bundle = document.getElementById("pippki_bundle");
+
+  var message1 = bundle.getFormattedString("editTrustCA", [cert.commonName]);
+  setText("certmsg", message1);
 
-  gCertDB.setCertTrust(gCert, Ci.nsIX509Cert.CA_CERT,
-                       trustSSL | trustEmail | trustObjSign);
+  var ssl = document.getElementById("trustSSL");
+  if (certdb.isCertTrusted(cert, nsIX509Cert.CA_CERT,
+                           nsIX509CertDB.TRUSTED_SSL)) {
+    ssl.setAttribute("checked", "true");
+  } else {
+    ssl.setAttribute("checked", "false");
+  }
+  var email = document.getElementById("trustEmail");
+  if (certdb.isCertTrusted(cert, nsIX509Cert.CA_CERT,
+                           nsIX509CertDB.TRUSTED_EMAIL)) {
+    email.setAttribute("checked", "true");
+  } else {
+    email.setAttribute("checked", "false");
+  }
+  var objsign = document.getElementById("trustObjSign");
+  if (certdb.isCertTrusted(cert, nsIX509Cert.CA_CERT,
+                           nsIX509CertDB.TRUSTED_OBJSIGN)) {
+    objsign.setAttribute("checked", "true");
+  } else {
+    objsign.setAttribute("checked", "false");
+  }
+}
+
+function doOK()
+{
+  var ssl = document.getElementById("trustSSL");
+  var email = document.getElementById("trustEmail");
+  var objsign = document.getElementById("trustObjSign");
+  var trustssl = (ssl.checked) ? nsIX509CertDB.TRUSTED_SSL : 0;
+  var trustemail = (email.checked) ? nsIX509CertDB.TRUSTED_EMAIL : 0;
+  var trustobjsign = (objsign.checked) ? nsIX509CertDB.TRUSTED_OBJSIGN : 0;
+  //
+  //  Set the cert trust
+  //
+  certdb.setCertTrust(cert, nsIX509Cert.CA_CERT,
+                      trustssl | trustemail | trustobjsign);
   return true;
 }
--- a/security/manager/pki/resources/jar.mn
+++ b/security/manager/pki/resources/jar.mn
@@ -14,17 +14,17 @@ pippki.jar:
     content/pippki/certManager.xul           (content/certManager.xul)
     content/pippki/CAOverlay.xul             (content/CAOverlay.xul)
     content/pippki/WebSitesOverlay.xul       (content/WebSitesOverlay.xul)
     content/pippki/OthersOverlay.xul         (content/OthersOverlay.xul)
     content/pippki/MineOverlay.xul           (content/MineOverlay.xul)
     content/pippki/OrphanOverlay.xul         (content/OrphanOverlay.xul)
     content/pippki/viewCertDetails.xul       (content/viewCertDetails.xul)
     content/pippki/editcacert.xul            (content/editcacert.xul)
-    content/pippki/editcacert.js             (content/editcacert.js)
+    content/pippki/editcerts.js              (content/editcerts.js)
 *   content/pippki/exceptionDialog.xul       (content/exceptionDialog.xul)
     content/pippki/exceptionDialog.js        (content/exceptionDialog.js)
     content/pippki/deletecert.xul            (content/deletecert.xul)
     content/pippki/deletecert.js             (content/deletecert.js)
     content/pippki/viewCertDetails.js        (content/viewCertDetails.js)
     content/pippki/setp12password.xul        (content/setp12password.xul)
     content/pippki/pippki.js                 (content/pippki.js)
     content/pippki/clientauthask.xul	     (content/clientauthask.xul)
--- a/security/manager/ssl/tests/mochitest/browser/browser.ini
+++ b/security/manager/ssl/tests/mochitest/browser/browser.ini
@@ -5,9 +5,8 @@ support-files =
   *.pem
 
 [browser_bug627234_perwindowpb.js]
 [browser_certificateManagerLeak.js]
 [browser_certViewer.js]
 [browser_clientAuth_connection.js]
 [browser_clientAuth_ui.js]
 [browser_deleteCert_ui.js]
-[browser_editCACertTrust.js]
--- a/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
@@ -5,118 +5,129 @@
 
 // Repeatedly opens the certificate viewer dialog with various certificates and
 // determines that the viewer correctly identifies either what usages those
 // certificates are valid for or what errors prevented the certificates from
 // being verified.
 
 var { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
 
+var certificates = [];
+
+registerCleanupFunction(function() {
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+  certificates.forEach(cert => {
+    certdb.deleteCertificate(cert);
+  });
+});
+
 add_task(function* () {
-  let cert = yield readCertificate("ca.pem", "CTu,CTu,CTu");
+  let cert = yield readCertificate("ca.pem", "CTu,CTu,CTu", certificates);
   let win = yield displayCertificate(cert);
   checkUsages(win, ["SSL Certificate Authority"]);
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("ssl-ee.pem", ",,");
+  let cert = yield readCertificate("ssl-ee.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkUsages(win, ["SSL Server Certificate", "SSL Client Certificate"]);
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("email-ee.pem", ",,");
+  let cert = yield readCertificate("email-ee.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkUsages(win, ["Email Recipient Certificate", "Email Signer Certificate"]);
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("code-ee.pem", ",,");
+  let cert = yield readCertificate("code-ee.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkUsages(win, ["Object Signer"]);
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("expired-ca.pem", ",,");
+  let cert = yield readCertificate("expired-ca.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkError(win, "Could not verify this certificate because it has expired.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("ee-from-expired-ca.pem", ",,");
+  let cert = yield readCertificate("ee-from-expired-ca.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because the CA certificate " +
              "is invalid.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("unknown-issuer.pem", ",,");
+  let cert = yield readCertificate("unknown-issuer.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because the issuer is " +
              "unknown.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("md5-ee.pem", ",,");
+  let cert = yield readCertificate("md5-ee.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because it was signed using " +
              "a signature algorithm that was disabled because that algorithm " +
              "is not secure.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("untrusted-ca.pem", "p,p,p");
+  let cert = yield readCertificate("untrusted-ca.pem", "p,p,p", certificates);
   let win = yield displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because it is not trusted.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
-  let cert = yield readCertificate("ee-from-untrusted-ca.pem", ",,");
+  let cert = yield readCertificate("ee-from-untrusted-ca.pem", ",,",
+                                   certificates);
   let win = yield displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because the issuer is not " +
              "trusted.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
   // Note that there's currently no way to un-do this. This should only be a
   // problem if another test re-uses a certificate with this same key (perhaps
   // likely) and subject (less likely).
   let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"]
                         .getService(Ci.nsICertBlocklist);
   certBlocklist.revokeCertBySubjectAndPubKey(
     "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
     "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="); // hash of the shared key
-  let cert = yield readCertificate("revoked.pem", ",,");
+  let cert = yield readCertificate("revoked.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkError(win,
              "Could not verify this certificate because it has been revoked.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 add_task(function* () {
   // This certificate has a keyUsage extension asserting cRLSign and
   // keyCertSign, but it doesn't have a basicConstraints extension. This
   // shouldn't be valid for any usage. Sadly, we give a pretty lame error
   // message in this case.
-  let cert = yield readCertificate("invalid.pem", ",,");
+  let cert = yield readCertificate("invalid.pem", ",,", certificates);
   let win = yield displayCertificate(cert);
   checkError(win, "Could not verify this certificate for unknown reasons.");
   yield BrowserTestUtils.closeWindow(win);
 });
 
 /**
  * Given a certificate, returns a promise that will resolve when the certificate
  * viewer has opened is displaying that certificate, and has finished
--- a/security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js
@@ -9,16 +9,17 @@
 // 2. The implementation correctly falls back through multiple cert attributes
 //    to determine what to display to represent a cert.
 
 /**
  * An array of tree items corresponding to TEST_CASES.
  * @type nsIMutableArray<nsICertTreeItem>
  */
 var gCertArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+var gImportedCerts = [];
 
 const FAKE_HOST_PORT = "Fake host and port";
 
 /**
  * @typedef {TestCase}
  * @type Object
  * @property {String} certFilename
  *           Filename of the cert, or null if we don't want to import a cert for
@@ -69,21 +70,29 @@ function openDeleteCertConfirmDialog(tab
   return new Promise((resolve, reject) => {
     win.addEventListener("load", function onLoad() {
       win.removeEventListener("load", onLoad);
       resolve([win, params]);
     });
   });
 }
 
+registerCleanupFunction(() => {
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+  for (let cert of gImportedCerts) {
+    certdb.deleteCertificate(cert);
+  }
+});
+
 add_task(function* setup() {
   for (let testCase of TEST_CASES) {
     let cert = null;
     if (testCase.certFilename) {
-      cert = yield readCertificate(testCase.certFilename, ",,");
+      cert = yield readCertificate(testCase.certFilename, ",,", gImportedCerts);
     }
     let certTreeItem = {
       hostPort: FAKE_HOST_PORT,
       cert: cert,
       QueryInterface(iid) {
         if (iid.equals(Ci.nsICertTreeItem)) {
           return this;
         }
deleted file mode 100644
--- a/security/manager/ssl/tests/mochitest/browser/browser_editCACertTrust.js
+++ /dev/null
@@ -1,119 +0,0 @@
-// Any copyright is dedicated to the Public Domain.
-// http://creativecommons.org/publicdomain/zero/1.0/
-"use strict";
-
-// Tests that the UI for editing the trust of a CA certificate correctly
-// reflects trust in the cert DB, and correctly updates trust in the cert DB
-// when requested.
-
-var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
-                .getService(Ci.nsIX509CertDB);
-
-/**
- * The cert we're editing the trust of.
- * @type nsIX509Cert
- */
-var gCert;
-
-/**
- * Opens the cert trust editing dialog.
- *
- * @returns {Promise}
- *          A promise that resolves when the dialog has finished loading with
- *          the window of the opened dialog.
- */
-function openEditCertTrustDialog() {
-  let win = window.openDialog("chrome://pippki/content/editcacert.xul", "", "",
-                              gCert);
-  return new Promise((resolve, reject) => {
-    win.addEventListener("load", function onLoad() {
-      win.removeEventListener("load", onLoad);
-      resolve(win);
-    });
-  });
-}
-
-add_task(function* setup() {
-  // Initially trust ca.pem for SSL, but not e-mail or object signing.
-  gCert = yield readCertificate("ca.pem", "CT,,");
-  Assert.ok(gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                  Ci.nsIX509CertDB.TRUSTED_SSL),
-            "Sanity check: ca.pem should be trusted for SSL");
-  Assert.ok(!gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                   Ci.nsIX509CertDB.TRUSTED_EMAIL),
-            "Sanity check: ca.pem should not be trusted for e-mail");
-  Assert.ok(!gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                   Ci.nsIX509CertDB.TRUSTED_OBJSIGN),
-            "Sanity check: ca.pem should not be trusted for object signing");
-});
-
-// Tests the following:
-// 1. The checkboxes correctly reflect the trust set in setup().
-// 2. Accepting the dialog after flipping some of the checkboxes results in the
-//    correct trust being set in the cert DB.
-add_task(function* testAcceptDialog() {
-  let win = yield openEditCertTrustDialog();
-
-  let sslCheckbox = win.document.getElementById("trustSSL");
-  let emailCheckbox = win.document.getElementById("trustEmail");
-  let objSignCheckbox = win.document.getElementById("trustObjSign");
-  Assert.ok(sslCheckbox.checked,
-            "Cert should be trusted for SSL in UI");
-  Assert.ok(!emailCheckbox.checked,
-            "Cert should not be trusted for e-mail in UI");
-  Assert.ok(!objSignCheckbox.checked,
-            "Cert should not be trusted for object signing in UI");
-
-  sslCheckbox.checked = false;
-  emailCheckbox.checked = true;
-
-  info("Accepting dialog");
-  win.document.getElementById("editCaCert").acceptDialog();
-  yield BrowserTestUtils.windowClosed(win);
-
-  Assert.ok(!gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                   Ci.nsIX509CertDB.TRUSTED_SSL),
-            "Cert should no longer be trusted for SSL");
-  Assert.ok(gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                  Ci.nsIX509CertDB.TRUSTED_EMAIL),
-            "Cert should now be trusted for e-mail");
-  Assert.ok(!gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                   Ci.nsIX509CertDB.TRUSTED_OBJSIGN),
-            "Cert should still not be trusted for object signing");
-});
-
-// Tests the following:
-// 1. The checkboxes correctly reflect the trust set in testAcceptDialog().
-// 2. Canceling the dialog even after flipping the checkboxes doesn't result in
-//    a change of trust in the cert DB.
-add_task(function* testCancelDialog() {
-  let win = yield openEditCertTrustDialog();
-
-  let sslCheckbox = win.document.getElementById("trustSSL");
-  let emailCheckbox = win.document.getElementById("trustEmail");
-  let objSignCheckbox = win.document.getElementById("trustObjSign");
-  Assert.ok(!sslCheckbox.checked,
-            "Cert should not be trusted for SSL in UI");
-  Assert.ok(emailCheckbox.checked,
-            "Cert should be trusted for e-mail in UI");
-  Assert.ok(!objSignCheckbox.checked,
-            "Cert should not be trusted for object signing in UI");
-
-  sslCheckbox.checked = true;
-  emailCheckbox.checked = false;
-  objSignCheckbox.checked = true;
-
-  info("Canceling dialog");
-  win.document.getElementById("editCaCert").cancelDialog();
-  yield BrowserTestUtils.windowClosed(win);
-
-  Assert.ok(!gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                  Ci.nsIX509CertDB.TRUSTED_SSL),
-            "Cert should still not be trusted for SSL");
-  Assert.ok(gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                  Ci.nsIX509CertDB.TRUSTED_EMAIL),
-            "Cert should still be trusted for e-mail");
-  Assert.ok(!gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
-                                   Ci.nsIX509CertDB.TRUSTED_OBJSIGN),
-            "Cert should still not be trusted for object signing");
-});
--- a/security/manager/ssl/tests/mochitest/browser/head.js
+++ b/security/manager/ssl/tests/mochitest/browser/head.js
@@ -1,59 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
-var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
-                .getService(Ci.nsIX509CertDB);
-
-/**
- * List of certs imported via readCertificate(). Certs in this list are
- * automatically deleted from the cert DB when a test including this head file
- * finishes.
- * @type nsIX509Cert[]
- */
-var gImportedCerts = [];
-
-registerCleanupFunction(() => {
-  for (let cert of gImportedCerts) {
-    gCertDB.deleteCertificate(cert);
-  }
-});
-
 /**
  * This function serves the same purpose as the one defined in head_psm.js.
  */
 function pemToBase64(pem) {
   return pem.replace(/-----BEGIN CERTIFICATE-----/, "")
             .replace(/-----END CERTIFICATE-----/, "")
             .replace(/[\r\n]/g, "");
 }
 
 /**
  * Given the filename of a certificate, returns a promise that will resolve with
  * a handle to the certificate when that certificate has been read and imported
  * with the given trust settings.
  *
- * Certs imported via this function will automatically be deleted from the cert
- * DB once the calling test finishes.
- *
  * @param {String} filename
  *        The filename of the certificate (assumed to be in the same directory).
  * @param {String} trustString
  *        A string describing how the certificate should be trusted (see
  *        `certutil -A --help`).
+ * @param {nsIX509Cert[]} certificates
+ *        An array to append the imported cert to. Useful for making sure
+ *        imported certs are cleaned up.
  * @return {Promise}
  *         A promise that will resolve with a handle to the certificate.
  */
-function readCertificate(filename, trustString) {
+function readCertificate(filename, trustString, certificates) {
   return OS.File.read(getTestFilePath(filename)).then(data => {
     let decoder = new TextDecoder();
     let pem = decoder.decode(data);
     let certdb = Cc["@mozilla.org/security/x509certdb;1"]
                    .getService(Ci.nsIX509CertDB);
     let base64 = pemToBase64(pem);
     certdb.addCertFromBase64(base64, trustString, "unused");
     let cert = certdb.constructX509FromBase64(base64);
-    gImportedCerts.push(cert);
+    certificates.push(cert);
     return cert;
   }, error => { throw error; });
 }
--- a/services/sync/modules/bookmark_validator.js
+++ b/services/sync/modules/bookmark_validator.js
@@ -36,16 +36,18 @@ this.EXPORTED_SYMBOLS = ["BookmarkValida
  *   had deleted parents
  * - childrenOnNonFolder (array of ids): list of non-folders that still have
  *   children arrays
  * - duplicateChildren (array of ids): list of records who have the same
  *   child listed multiple times in their children array
  * - parentNotFolder (array of ids): list of records that have parents that
  *   aren't folders
  * - rootOnServer (boolean): true if the root came from the server
+ * - badClientRoots (array of ids): Contains any client-side root ids where
+ *   the root is missing or isn't a (direct) child of the places root.
  *
  * - clientMissing: Array of ids on the server missing from the client
  * - serverMissing: Array of ids on the client missing from the server
  * - serverDeleted: Array of ids on the client that the server had marked as deleted.
  * - serverUnexpected: Array of ids that appear on the server but shouldn't
  *   because the client attempts to never upload them.
  * - differences: Array of {id: string, differences: string array} recording
  *   the non-structural properties that are differente between the client and server
@@ -65,16 +67,17 @@ class BookmarkProblemData {
     this.missingChildren = [];
     this.deletedChildren = [];
     this.multipleParents = [];
     this.deletedParents = [];
     this.childrenOnNonFolder = [];
     this.duplicateChildren = [];
     this.parentNotFolder = [];
 
+    this.badClientRoots = [];
     this.clientMissing = [];
     this.serverMissing = [];
     this.serverDeleted = [];
     this.serverUnexpected = [];
     this.differences = [];
     this.structuralDifferences = [];
   }
 
@@ -117,16 +120,17 @@ class BookmarkProblemData {
 
       { name: "missingIDs", count: this.missingIDs },
       { name: "rootOnServer", count: this.rootOnServer ? 1 : 0 },
 
       { name: "duplicates", count: this.duplicates.length },
       { name: "parentChildMismatches", count: this.parentChildMismatches.length },
       { name: "cycles", count: this.cycles.length },
       { name: "clientCycles", count: this.clientCycles.length },
+      { name: "badClientRoots", count: this.badClientRoots.length },
       { name: "orphans", count: this.orphans.length },
       { name: "missingChildren", count: this.missingChildren.length },
       { name: "deletedChildren", count: this.deletedChildren.length },
       { name: "multipleParents", count: this.multipleParents.length },
       { name: "deletedParents", count: this.deletedParents.length },
       { name: "childrenOnNonFolder", count: this.childrenOnNonFolder.length },
       { name: "duplicateChildren", count: this.duplicateChildren.length },
       { name: "parentNotFolder", count: this.parentNotFolder.length },
@@ -553,16 +557,34 @@ class BookmarkValidator {
       if (!seenEver.has(record)) {
         traverse(record);
       }
     }
 
     return cycles;
   }
 
+  // Perform client-side sanity checking that doesn't involve server data
+  _validateClient(problemData, clientRecords) {
+    problemData.clientCycles = this._detectCycles(clientRecords);
+    const rootsToCheck = [
+      PlacesUtils.bookmarks.menuGuid,
+      PlacesUtils.bookmarks.toolbarGuid,
+      PlacesUtils.bookmarks.unfiledGuid,
+      PlacesUtils.bookmarks.mobileGuid,
+    ];
+    for (let rootGUID of rootsToCheck) {
+      let record = clientRecords.find(record =>
+        record.guid === rootGUID);
+      if (!record || record.parentid !== "places") {
+        problemData.badClientRoots.push(rootGUID);
+      }
+    }
+  }
+
   /**
    * Compare the list of server records with the client tree.
    *
    * Returns the same data as described in the inspectServerRecords comment,
    * with the following additional fields.
    * - clientRecords: an array of client records in a similar format to
    *   the .records (ie, server records) entry.
    * - problemData is the same as for inspectServerRecords, except all properties
@@ -573,17 +595,17 @@ class BookmarkValidator {
     let clientRecords = this.createClientRecordsFromTree(clientTree);
     let inspectionInfo = this.inspectServerRecords(serverRecords);
     inspectionInfo.clientRecords = clientRecords;
 
     // Mainly do this to remove deleted items and normalize child guids.
     serverRecords = inspectionInfo.records;
     let problemData = inspectionInfo.problemData;
 
-    problemData.clientCycles = this._detectCycles(clientRecords);
+    this._validateClient(problemData, clientRecords);
 
     let matches = [];
 
     let allRecords = new Map();
     let serverDeletedLookup = new Set(inspectionInfo.deletedRecords.map(r => r.id));
 
     for (let sr of serverRecords) {
       allRecords.set(sr.id, {client: null, server: sr});
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -47,16 +47,36 @@ const ALLBOOKMARKS_ANNO = "AllBookmarks"
 const MOBILE_ANNO = "MobileBookmarks";
 
 // The tracker ignores changes made by bookmark import and restore, and
 // changes made by Sync. We don't need to exclude `SOURCE_IMPORT`, but both
 // import and restore fire `bookmarks-restore-*` observer notifications, and
 // the tracker doesn't currently distinguish between the two.
 const IGNORED_SOURCES = [SOURCE_SYNC, SOURCE_IMPORT, SOURCE_IMPORT_REPLACE];
 
+// Returns the constructor for a bookmark record type.
+function getTypeObject(type) {
+  switch (type) {
+    case "bookmark":
+    case "microsummary":
+      return Bookmark;
+    case "query":
+      return BookmarkQuery;
+    case "folder":
+      return BookmarkFolder;
+    case "livemark":
+      return Livemark;
+    case "separator":
+      return BookmarkSeparator;
+    case "item":
+      return PlacesItem;
+  }
+  return null;
+}
+
 this.PlacesItem = function PlacesItem(collection, id, type) {
   CryptoWrapper.call(this, collection, id);
   this.type = type || "item";
 }
 PlacesItem.prototype = {
   decrypt: function PlacesItem_decrypt(keyBundle) {
     // Do the normal CryptoWrapper decrypt, but change types before returning
     let clear = CryptoWrapper.prototype.decrypt.call(this, keyBundle);
@@ -64,46 +84,42 @@ PlacesItem.prototype = {
     // Convert the abstract places item to the actual object type
     if (!this.deleted)
       this.__proto__ = this.getTypeObject(this.type).prototype;
 
     return clear;
   },
 
   getTypeObject: function PlacesItem_getTypeObject(type) {
-    switch (type) {
-      case "bookmark":
-      case "microsummary":
-        return Bookmark;
-      case "query":
-        return BookmarkQuery;
-      case "folder":
-        return BookmarkFolder;
-      case "livemark":
-        return Livemark;
-      case "separator":
-        return BookmarkSeparator;
-      case "item":
-        return PlacesItem;
+    let recordObj = getTypeObject(type);
+    if (!recordObj) {
+      throw new Error("Unknown places item object type: " + type);
     }
-    throw "Unknown places item object type: " + type;
+    return recordObj;
   },
 
   __proto__: CryptoWrapper.prototype,
   _logName: "Sync.Record.PlacesItem",
 
   // Converts the record to a Sync bookmark object that can be passed to
   // `PlacesSyncUtils.bookmarks.{insert, update}`.
   toSyncBookmark() {
     return {
       kind: this.type,
       syncId: this.id,
       parentSyncId: this.parentid,
     };
   },
+
+  // Populates the record from a Sync bookmark object returned from
+  // `PlacesSyncUtils.bookmarks.fetch`.
+  fromSyncBookmark(item) {
+    this.parentid = item.parentSyncId;
+    this.parentName = item.parentTitle;
+  },
 };
 
 Utils.deferGetSet(PlacesItem,
                   "cleartext",
                   ["hasDupe", "parentid", "parentName", "type"]);
 
 this.Bookmark = function Bookmark(collection, id, type) {
   PlacesItem.call(this, collection, id, type || "bookmark");
@@ -117,16 +133,26 @@ Bookmark.prototype = {
     info.title = this.title;
     info.url = this.bmkUri;
     info.description = this.description;
     info.loadInSidebar = this.loadInSidebar;
     info.tags = this.tags;
     info.keyword = this.keyword;
     return info;
   },
+
+  fromSyncBookmark(item) {
+    PlacesItem.prototype.fromSyncBookmark.call(this, item);
+    this.title = item.title;
+    this.bmkUri = item.url.href;
+    this.description = item.description;
+    this.loadInSidebar = item.loadInSidebar;
+    this.tags = item.tags;
+    this.keyword = item.keyword;
+  },
 };
 
 Utils.deferGetSet(Bookmark,
                   "cleartext",
                   ["title", "bmkUri", "description",
                    "loadInSidebar", "tags", "keyword"]);
 
 this.BookmarkQuery = function BookmarkQuery(collection, id) {
@@ -137,16 +163,22 @@ BookmarkQuery.prototype = {
   _logName: "Sync.Record.BookmarkQuery",
 
   toSyncBookmark() {
     let info = Bookmark.prototype.toSyncBookmark.call(this);
     info.folder = this.folderName;
     info.query = this.queryId;
     return info;
   },
+
+  fromSyncBookmark(item) {
+    Bookmark.prototype.fromSyncBookmark.call(this, item);
+    this.folderName = item.folder;
+    this.queryId = item.query;
+  },
 };
 
 Utils.deferGetSet(BookmarkQuery,
                   "cleartext",
                   ["folderName", "queryId"]);
 
 this.BookmarkFolder = function BookmarkFolder(collection, id, type) {
   PlacesItem.call(this, collection, id, type || "folder");
@@ -156,16 +188,23 @@ BookmarkFolder.prototype = {
   _logName: "Sync.Record.Folder",
 
   toSyncBookmark() {
     let info = PlacesItem.prototype.toSyncBookmark.call(this);
     info.description = this.description;
     info.title = this.title;
     return info;
   },
+
+  fromSyncBookmark(item) {
+    PlacesItem.prototype.fromSyncBookmark.call(this, item);
+    this.title = item.title;
+    this.description = item.description;
+    this.children = item.childSyncIds;
+  },
 };
 
 Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title",
                                                 "children"]);
 
 this.Livemark = function Livemark(collection, id) {
   BookmarkFolder.call(this, collection, id, "livemark");
 }
@@ -174,26 +213,39 @@ Livemark.prototype = {
   _logName: "Sync.Record.Livemark",
 
   toSyncBookmark() {
     let info = BookmarkFolder.prototype.toSyncBookmark.call(this);
     info.feed = this.feedUri;
     info.site = this.siteUri;
     return info;
   },
+
+  fromSyncBookmark(item) {
+    BookmarkFolder.prototype.fromSyncBookmark.call(this, item);
+    this.feedUri = item.feed.href;
+    if (item.site) {
+      this.siteUri = item.site.href;
+    }
+  },
 };
 
 Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);
 
 this.BookmarkSeparator = function BookmarkSeparator(collection, id) {
   PlacesItem.call(this, collection, id, "separator");
 }
 BookmarkSeparator.prototype = {
   __proto__: PlacesItem.prototype,
   _logName: "Sync.Record.Separator",
+
+  fromSyncBookmark(item) {
+    PlacesItem.prototype.fromSyncBookmark.call(this, item);
+    this.pos = item.index;
+  },
 };
 
 Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos");
 
 this.BookmarksEngine = function BookmarksEngine(service) {
   SyncEngine.call(this, "Bookmarks", service);
 }
 BookmarksEngine.prototype = {
@@ -711,131 +763,33 @@ BookmarksStore.prototype = {
   },
 
   changeItemID: function BStore_changeItemID(oldID, newID) {
     this._log.debug("Changing GUID " + oldID + " to " + newID);
 
     Async.promiseSpinningly(PlacesSyncUtils.bookmarks.changeGuid(oldID, newID));
   },
 
-  _getTags: function BStore__getTags(uri) {
-    try {
-      if (typeof(uri) == "string")
-        uri = Utils.makeURI(uri);
-    } catch(e) {
-      this._log.warn("Could not parse URI \"" + uri + "\": " + e);
-    }
-    return PlacesUtils.tagging.getTagsForURI(uri, {});
-  },
-
-  _getDescription: function BStore__getDescription(id) {
-    try {
-      return PlacesUtils.annotations.getItemAnnotation(id,
-        PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO);
-    } catch (e) {
-      return null;
-    }
-  },
-
-  _isLoadInSidebar: function BStore__isLoadInSidebar(id) {
-    return PlacesUtils.annotations.itemHasAnnotation(id,
-      PlacesSyncUtils.bookmarks.SIDEBAR_ANNO);
-  },
-
   // Create a record starting from the weave id (places guid)
   createRecord: function createRecord(id, collection) {
-    let placeId = this.idForGUID(id);
-    let record;
-    if (placeId <= 0) { // deleted item
-      record = new PlacesItem(collection, id);
+    let item = Async.promiseSpinningly(PlacesSyncUtils.bookmarks.fetch(id));
+    if (!item) { // deleted item
+      let record = new PlacesItem(collection, id);
       record.deleted = true;
       return record;
     }
 
-    let parent = PlacesUtils.bookmarks.getFolderIdForItem(placeId);
-    switch (PlacesUtils.bookmarks.getItemType(placeId)) {
-    case PlacesUtils.bookmarks.TYPE_BOOKMARK:
-      let bmkUri = PlacesUtils.bookmarks.getBookmarkURI(placeId).spec;
-      if (bmkUri.indexOf("place:") == 0) {
-        record = new BookmarkQuery(collection, id);
-
-        // Get the actual tag name instead of the local itemId
-        let folder = bmkUri.match(/[:&]folder=(\d+)/);
-        try {
-          // There might not be the tag yet when creating on a new client
-          if (folder != null) {
-            folder = folder[1];
-            record.folderName = PlacesUtils.bookmarks.getItemTitle(folder);
-            this._log.trace("query id: " + folder + " = " + record.folderName);
-          }
-        }
-        catch(ex) {}
-
-        // Persist the Smart Bookmark anno, if found.
-        try {
-          let anno = PlacesUtils.annotations.getItemAnnotation(placeId,
-            PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO);
-          if (anno != null) {
-            this._log.trace("query anno: " +
-                            PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO +
-                            " = " + anno);
-            record.queryId = anno;
-          }
-        }
-        catch(ex) {}
-      }
-      else {
-        record = new Bookmark(collection, id);
-      }
-      record.title = PlacesUtils.bookmarks.getItemTitle(placeId);
+    let recordObj = getTypeObject(item.kind);
+    if (!recordObj) {
+      this._log.warn("Unknown item type, cannot serialize: " + item.kind);
+      recordObj = PlacesItem;
+    }
+    let record = new recordObj(collection, id);
+    record.fromSyncBookmark(item);
 
-      record.parentName = PlacesUtils.bookmarks.getItemTitle(parent);
-      record.bmkUri = bmkUri;
-      record.tags = this._getTags(record.bmkUri);
-      record.keyword = PlacesUtils.bookmarks.getKeywordForBookmark(placeId);
-      record.description = this._getDescription(placeId);
-      record.loadInSidebar = this._isLoadInSidebar(placeId);
-      break;
-
-    case PlacesUtils.bookmarks.TYPE_FOLDER:
-      if (PlacesUtils.annotations
-                     .itemHasAnnotation(placeId, PlacesUtils.LMANNO_FEEDURI)) {
-        record = new Livemark(collection, id);
-        let as = PlacesUtils.annotations;
-        record.feedUri = as.getItemAnnotation(placeId, PlacesUtils.LMANNO_FEEDURI);
-        try {
-          record.siteUri = as.getItemAnnotation(placeId, PlacesUtils.LMANNO_SITEURI);
-        } catch (ex) {}
-      } else {
-        record = new BookmarkFolder(collection, id);
-      }
-
-      if (parent > 0)
-        record.parentName = PlacesUtils.bookmarks.getItemTitle(parent);
-      record.title = PlacesUtils.bookmarks.getItemTitle(placeId);
-      record.description = this._getDescription(placeId);
-      record.children = Async.promiseSpinningly(
-        PlacesSyncUtils.bookmarks.fetchChildSyncIds(id));
-      break;
-
-    case PlacesUtils.bookmarks.TYPE_SEPARATOR:
-      record = new BookmarkSeparator(collection, id);
-      if (parent > 0)
-        record.parentName = PlacesUtils.bookmarks.getItemTitle(parent);
-      // Create a positioning identifier for the separator, used by _mapDupe
-      record.pos = PlacesUtils.bookmarks.getItemIndex(placeId);
-      break;
-
-    default:
-      record = new PlacesItem(collection, id);
-      this._log.warn("Unknown item type, cannot serialize: " +
-                     PlacesUtils.bookmarks.getItemType(placeId));
-    }
-
-    record.parentid = this.GUIDForId(parent);
     record.sortindex = this._calculateIndex(record);
 
     return record;
   },
 
   _stmts: {},
   _getStmt: function(query) {
     if (query in this._stmts) {
@@ -1138,19 +1092,19 @@ BookmarksTracker.prototype = {
     }
     // Make sure the existing title is correct
     else {
       let queryTitle = PlacesUtils.bookmarks.getItemTitle(mobile[0]);
       if (queryTitle != title) {
         PlacesUtils.bookmarks.setItemTitle(mobile[0], title, SOURCE_SYNC);
       }
       let rootTitle =
-        PlacesUtils.bookmarks.getItemTitle(BookmarkSpecialIds.mobile);
+        PlacesUtils.bookmarks.getItemTitle(PlacesUtils.mobileFolderId);
       if (rootTitle != title) {
-        PlacesUtils.bookmarks.setItemTitle(BookmarkSpecialIds.mobile, title,
+        PlacesUtils.bookmarks.setItemTitle(PlacesUtils.mobileFolderId, title,
                                            SOURCE_SYNC);
       }
     }
   },
 
   // This method is oddly structured, but the idea is to return as quickly as
   // possible -- this handler gets called *every time* a bookmark changes, for
   // *each change*.
--- a/services/sync/tests/unit/test_bookmark_tracker.js
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -4,16 +4,17 @@
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
 
 Service.engineManager.register(BookmarksEngine);
 var engine = Service.engineManager.get("bookmarks");
 var store  = engine._store;
 var tracker = engine._tracker;
 
 store.wipe();
 tracker.persistChangedIDs = false;
@@ -61,16 +62,23 @@ function* verifyTrackedItems(tracked) {
     JSON.stringify(Array.from(trackedIDs))}`);
 }
 
 function* verifyTrackedCount(expected) {
   let changes = engine.pullNewChanges();
   equal(changes.count(), expected);
 }
 
+// Copied from PlacesSyncUtils.jsm.
+function findAnnoItems(anno, val) {
+  let annos = PlacesUtils.annotations;
+  return annos.getItemsWithAnnotation(anno, {}).filter(id =>
+    annos.getItemAnnotation(id, anno) == val);
+}
+
 add_task(function* test_tracking() {
   _("Test starting and stopping the tracker");
 
   let folder = PlacesUtils.bookmarks.createFolder(
     PlacesUtils.bookmarks.bookmarksMenuFolder,
     "Test Folder", PlacesUtils.bookmarks.DEFAULT_INDEX);
   function createBmk() {
     return PlacesUtils.bookmarks.insertBookmark(
@@ -1428,16 +1436,74 @@ add_task(function* test_onItemDeleted_tr
     yield verifyTrackedItems([fx_guid, tb_guid, folder1_guid, folder2_guid]);
     do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6);
   } finally {
     _("Clean up.");
     yield cleanup();
   }
 });
 
+add_task(function* test_mobile_query() {
+  _("Ensure we correctly create the mobile query");
+
+  try {
+    // Creates the organizer queries as a side effect.
+    let leftPaneId = PlacesUIUtils.leftPaneFolderId;
+    _(`Left pane root ID: ${leftPaneId}`);
+
+    let allBookmarksIds = findAnnoItems("PlacesOrganizer/OrganizerQuery", "AllBookmarks");
+    equal(allBookmarksIds.length, 1, "Should create folder with all bookmarks queries");
+    let allBookmarkGuid = yield PlacesUtils.promiseItemGuid(allBookmarksIds[0]);
+
+    _("Try creating query after organizer is ready");
+    tracker._ensureMobileQuery();
+    let queryIds = findAnnoItems("PlacesOrganizer/OrganizerQuery", "MobileBookmarks");
+    equal(queryIds.length, 0, "Should not create query without any mobile bookmarks");
+
+    _("Insert mobile bookmark, then create query");
+    yield PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.mobileGuid,
+      url: "https://mozilla.org",
+    });
+    tracker._ensureMobileQuery();
+    queryIds = findAnnoItems("PlacesOrganizer/OrganizerQuery", "MobileBookmarks", {});
+    equal(queryIds.length, 1, "Should create query once mobile bookmarks exist");
+
+    let queryId = queryIds[0];
+    let queryGuid = yield PlacesUtils.promiseItemGuid(queryId);
+
+    let queryInfo = yield PlacesUtils.bookmarks.fetch(queryGuid);
+    equal(queryInfo.url, `place:folder=${PlacesUtils.mobileFolderId}`, "Query should point to mobile root");
+    equal(queryInfo.title, "Mobile Bookmarks", "Query title should be localized");
+    equal(queryInfo.parentGuid, allBookmarkGuid, "Should append mobile query to all bookmarks queries");
+
+    _("Rename root and query, then recreate");
+    yield PlacesUtils.bookmarks.update({
+      guid: PlacesUtils.bookmarks.mobileGuid,
+      title: "renamed root",
+    });
+    yield PlacesUtils.bookmarks.update({
+      guid: queryGuid,
+      title: "renamed query",
+    });
+    tracker._ensureMobileQuery();
+    let rootInfo = yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.mobileGuid);
+    equal(rootInfo.title, "Mobile Bookmarks", "Should fix root title");
+    queryInfo = yield PlacesUtils.bookmarks.fetch(queryGuid);
+    equal(queryInfo.title, "Mobile Bookmarks", "Should fix query title");
+
+    _("We shouldn't track the query or the left pane root");
+    yield verifyTrackedCount(0);
+    do_check_eq(tracker.score, 0);
+  } finally {
+    _("Clean up.");
+    yield cleanup();
+  }
+});
+
 function run_test() {
   initTestLogging("Trace");
 
   Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.Store.Bookmarks").level = Log.Level.Trace;
   Log.repository.getLogger("Sync.Tracker.Bookmarks").level = Log.Level.Trace;
 
   run_next_test();
--- a/services/sync/tests/unit/test_bookmark_validator.js
+++ b/services/sync/tests/unit/test_bookmark_validator.js
@@ -266,16 +266,17 @@ add_task(function *test_telemetry_integr
   let bme = ping.engines.find(e => e.name === "bookmarks");
   ok(bme);
   ok(bme.validation);
   ok(bme.validation.problems)
   equal(bme.validation.checked, server.length);
   equal(bme.validation.took, duration);
   bme.validation.problems.sort((a, b) => String.localeCompare(a.name, b.name));
   deepEqual(bme.validation.problems, [
+    { name: "badClientRoots", count: 4 },
     { name: "sdiff:childGUIDs", count: 1 },
     { name: "serverMissing", count: 1 },
     { name: "structuralDifferences", count: 1 },
   ]);
 });
 
 function run_test() {
   run_next_test();
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -259,16 +259,93 @@ const BookmarkSyncUtils = PlacesSyncUtil
    * @resolves to an object representing the created bookmark.
    * @rejects if it's not possible to create the requested bookmark.
    * @throws if the arguments are invalid.
    */
   insert: Task.async(function* (info) {
     let insertInfo = validateNewBookmark(info);
     return insertSyncBookmark(insertInfo);
   }),
+
+  /**
+   * Fetches a Sync bookmark object for an item in the tree. The object contains
+   * the following properties, depending on the item's kind:
+   *
+   *  - kind (all): A string representing the item's kind.
+   *  - syncId (all): The item's sync ID.
+   *  - parentSyncId (all): The sync ID of the item's parent.
+   *  - parentTitle (all): The title of the item's parent, used for de-duping.
+   *    Omitted for the Places root and parents with empty titles.
+   *  - title ("bookmark", "folder", "livemark", "query"): The item's title.
+   *    Omitted if empty.
+   *  - url ("bookmark", "query"): The item's URL.
+   *  - tags ("bookmark", "query"): An array containing the item's tags.
+   *  - keyword ("bookmark"): The bookmark's keyword, if one exists.
+   *  - description ("bookmark", "folder", "livemark"): The item's description.
+   *    Omitted if one isn't set.
+   *  - loadInSidebar ("bookmark", "query"): Whether to load the bookmark in
+   *    the sidebar. Always `false` for queries.
+   *  - feed ("livemark"): A `URL` object pointing to the livemark's feed URL.
+   *  - site ("livemark"): A `URL` object pointing to the livemark's site URL,
+   *    or `null` if one isn't set.
+   *  - childSyncIds ("folder"): An array containing the sync IDs of the item's
+   *    children, used to determine child order.
+   *  - folder ("query"): The tag folder name, if this is a tag query.
+   *  - query ("query"): The smart bookmark query name, if this is a smart
+   *    bookmark.
+   *  - index ("separator"): The separator's position within its parent.
+   */
+  fetch: Task.async(function* (syncId) {
+    let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
+    let bookmarkItem = yield PlacesUtils.bookmarks.fetch(guid);
+    if (!bookmarkItem) {
+      return null;
+    }
+
+    // Convert the Places bookmark object to a Sync bookmark and add
+    // kind-specific properties.
+    let kind = yield getKindForItem(bookmarkItem);
+    let item;
+    switch (kind) {
+      case BookmarkSyncUtils.KINDS.BOOKMARK:
+      case BookmarkSyncUtils.KINDS.MICROSUMMARY:
+        item = yield fetchBookmarkItem(bookmarkItem);
+        break;
+
+      case BookmarkSyncUtils.KINDS.QUERY:
+        item = yield fetchQueryItem(bookmarkItem);
+        break;
+
+      case BookmarkSyncUtils.KINDS.FOLDER:
+        item = yield fetchFolderItem(bookmarkItem);
+        break;
+
+      case BookmarkSyncUtils.KINDS.LIVEMARK:
+        item = yield fetchLivemarkItem(bookmarkItem);
+        break;
+
+      case BookmarkSyncUtils.KINDS.SEPARATOR:
+        item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+        item.index = bookmarkItem.index;
+        break;
+
+      default:
+        throw new Error(`Unknown bookmark kind: ${kind}`);
+    }
+
+    // Sync uses the parent title for de-duping.
+    if (bookmarkItem.parentGuid) {
+      let parent = yield PlacesUtils.bookmarks.fetch(bookmarkItem.parentGuid);
+      if ("title" in parent) {
+        item.parentTitle = parent.title;
+      }
+    }
+
+    return item;
+  }),
 });
 
 XPCOMUtils.defineLazyGetter(this, "BookmarkSyncLog", () => {
   return Log.repository.getLogger("BookmarkSyncUtils");
 });
 
 function validateSyncBookmarkObject(input, behavior) {
   return PlacesUtils.validateItemProperties(
@@ -967,8 +1044,134 @@ function syncBookmarkToPlacesBookmark(in
           bookmarkInfo.siteURI = PlacesUtils.toURI(info.site);
         }
         break;
     }
   }
 
   return bookmarkInfo;
 }
+
+// Creates and returns a Sync bookmark object containing the bookmark's
+// tags, keyword, description, and whether it loads in the sidebar.
+var fetchBookmarkItem = Task.async(function* (bookmarkItem) {
+  let itemId = yield PlacesUtils.promiseItemId(bookmarkItem.guid);
+  let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+  item.tags = PlacesUtils.tagging.getTagsForURI(
+    PlacesUtils.toURI(bookmarkItem.url), {});
+
+  let keywordEntry = yield PlacesUtils.keywords.fetch({
+    url: bookmarkItem.url,
+  });
+  if (keywordEntry) {
+    item.keyword = keywordEntry.keyword;
+  }
+
+  let description = getItemDescription(itemId);
+  if (description) {
+    item.description = description;
+  }
+
+  item.loadInSidebar = PlacesUtils.annotations.itemHasAnnotation(itemId,
+    BookmarkSyncUtils.SIDEBAR_ANNO);
+
+  return item;
+});
+
+// Creates and returns a Sync bookmark object containing the folder's
+// description and children.
+var fetchFolderItem = Task.async(function* (bookmarkItem) {
+  let itemId = yield PlacesUtils.promiseItemId(bookmarkItem.guid);
+  let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+  let description = getItemDescription(itemId);
+  if (description) {
+    item.description = description;
+  }
+
+  let db = yield PlacesUtils.promiseDBConnection();
+  let children = yield fetchAllChildren(db, bookmarkItem.guid);
+  item.childSyncIds = children.map(child =>
+    BookmarkSyncUtils.guidToSyncId(child.guid)
+  );
+
+  return item;
+});
+
+// Creates and returns a Sync bookmark object containing the livemark's
+// description, children (none), feed URI, and site URI.
+var fetchLivemarkItem = Task.async(function* (bookmarkItem) {
+  let itemId = yield PlacesUtils.promiseItemId(bookmarkItem.guid);
+  let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+  let description = getItemDescription(itemId);
+  if (description) {
+    item.description = description;
+  }
+
+  let feedAnno = PlacesUtils.annotations.getItemAnnotation(itemId,
+    PlacesUtils.LMANNO_FEEDURI);
+  item.feed = new URL(feedAnno);
+
+  let siteAnno = null;
+  try {
+    siteAnno = PlacesUtils.annotations.getItemAnnotation(itemId,
+      PlacesUtils.LMANNO_SITEURI);
+  } catch (ex) {}
+  if (siteAnno != null) {
+    item.site = new URL(siteAnno);
+  }
+
+  return item;
+});
+
+// Creates and returns a Sync bookmark object containing the query's tag
+// folder name and smart bookmark query ID.
+var fetchQueryItem = Task.async(function* (bookmarkItem) {
+  let itemId = yield PlacesUtils.promiseItemId(bookmarkItem.guid);
+  let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+  let description = getItemDescription(itemId);
+  if (description) {
+    item.description = description;
+  }
+
+  let folder = null;
+  let params = new URLSearchParams(bookmarkItem.url.pathname);
+  let tagFolderId = +params.get("folder");
+  if (tagFolderId) {
+    try {
+      let tagFolderGuid = yield PlacesUtils.promiseItemGuid(tagFolderId);
+      let tagFolder = yield PlacesUtils.bookmarks.fetch(tagFolderGuid);
+      folder = tagFolder.title;
+    } catch (ex) {
+      BookmarkSyncLog.warn("fetchQueryItem: Query " + bookmarkItem.url.href +
+                           " points to nonexistent folder " + tagFolderId, ex);
+    }
+  }
+  if (folder != null) {
+    item.folder = folder;
+  }
+
+  let query = null;
+  try {
+    // Throws if the bookmark doesn't have the smart bookmark anno.
+    query = PlacesUtils.annotations.getItemAnnotation(itemId,
+      BookmarkSyncUtils.SMART_BOOKMARKS_ANNO);
+  } catch (ex) {}
+  if (query != null) {
+    item.query = query;
+  }
+
+  return item;
+});
+
+// Returns an item's description, or `null` if one isn't set.
+function getItemDescription(id) {
+  try {
+    return PlacesUtils.annotations.getItemAnnotation(id,
+      BookmarkSyncUtils.DESCRIPTION_ANNO);
+  } catch (ex) {}
+  return null;
+}
+
+
--- a/toolkit/components/places/tests/unit/test_sync_utils.js
+++ b/toolkit/components/places/tests/unit/test_sync_utils.js
@@ -338,23 +338,20 @@ add_task(function* test_update_keyword()
 
   yield PlacesUtils.bookmarks.eraseEverything();
 });
 
 add_task(function* test_update_annos() {
   let guids = yield populateTree(PlacesUtils.bookmarks.menuGuid, {
     kind: "folder",
     title: "folder",
-    description: "Folder description",
   }, {
     kind: "bookmark",
     title: "bmk",
     url: "https://example.com",
-    description: "Bookmark description",
-    loadInSidebar: true,
   });
 
   do_print("Add folder description");
   {
     let updatedItem = yield PlacesSyncUtils.bookmarks.update({
       syncId: guids.folder,
       description: "Folder description",
     });
@@ -999,8 +996,150 @@ add_task(function* test_insert_orphans()
 
     let child = yield PlacesUtils.bookmarks.fetch({ guid: childGuid });
     equal(child.parentGuid, parentGuid,
       "Should reparent child after inserting missing parent");
   }
 
   yield PlacesUtils.bookmarks.eraseEverything();
 });
+
+add_task(function* test_fetch() {
+  let folder = yield PlacesSyncUtils.bookmarks.insert({
+    syncId: makeGuid(),
+    parentSyncId: "menu",
+    kind: "folder",
+    description: "Folder description",
+  });
+  let bmk = yield PlacesSyncUtils.bookmarks.insert({
+    syncId: makeGuid(),
+    parentSyncId: "menu",
+    kind: "bookmark",
+    url: "https://example.com",
+    description: "Bookmark description",
+    loadInSidebar: true,
+    tags: ["taggy"],
+  });
+  let folderBmk = yield PlacesSyncUtils.bookmarks.insert({
+    syncId: makeGuid(),
+    parentSyncId: folder.syncId,
+    kind: "bookmark",
+    url: "https://example.org",
+    keyword: "kw",
+  });
+  let folderSep = yield PlacesSyncUtils.bookmarks.insert({
+    syncId: makeGuid(),
+    parentSyncId: folder.syncId,
+    kind: "separator",
+  });
+  let tagQuery = yield PlacesSyncUtils.bookmarks.insert({
+    kind: "query",
+    syncId: makeGuid(),
+    parentSyncId: "toolbar",
+    url: "place:type=7&folder=90",
+    folder: "taggy",
+    title: "Tagged stuff",
+  });
+  let [, tagFolderId] = /\bfolder=(\d+)\b/.exec(tagQuery.url.pathname);
+  let smartBmk = yield PlacesSyncUtils.bookmarks.insert({
+    kind: "query",
+    syncId: makeGuid(),
+    parentSyncId: "toolbar",
+    url: "place:folder=TOOLBAR",
+    query: "BookmarksToolbar",
+    title: "Bookmarks toolbar query",
+  });
+
+  do_print("Fetch empty folder with description");
+  {
+    let item = yield PlacesSyncUtils.bookmarks.fetch(folder.syncId);
+    deepEqual(item, {
+      syncId: folder.syncId,
+      kind: "folder",
+      parentSyncId: "menu",
+      description: "Folder description",
+      childSyncIds: [folderBmk.syncId, folderSep.syncId],
+      parentTitle: "Bookmarks Menu",
+    }, "Should include description, children, and parent title in folder");
+  }
+
+  do_print("Fetch bookmark with description, sidebar anno, and tags");
+  {
+    let item = yield PlacesSyncUtils.bookmarks.fetch(bmk.syncId);
+    deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId", "url",
+      "tags", "description", "loadInSidebar", "parentTitle"].sort(),
+      "Should include bookmark-specific properties");
+    equal(item.syncId, bmk.syncId, "Sync ID should match");
+    equal(item.url.href, "https://example.com/", "Should return URL");
+    equal(item.parentSyncId, "menu", "Should return parent sync ID");
+    deepEqual(item.tags, ["taggy"], "Should return tags");
+    equal(item.description, "Bookmark description", "Should return bookmark description");
+    strictEqual(item.loadInSidebar, true, "Should return sidebar anno");
+    equal(item.parentTitle, "Bookmarks Menu", "Should return parent title");
+  }
+
+  do_print("Fetch bookmark with keyword; without parent title or annos");
+  {
+    let item = yield PlacesSyncUtils.bookmarks.fetch(folderBmk.syncId);
+    deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
+      "url", "keyword", "tags", "loadInSidebar"].sort(),
+      "Should omit blank bookmark-specific properties");
+    strictEqual(item.loadInSidebar, false, "Should not load bookmark in sidebar");
+    deepEqual(item.tags, [], "Tags should be empty");
+    equal(item.keyword, "kw", "Should return keyword");
+  }
+
+  do_print("Fetch separator");
+  {
+    let item = yield PlacesSyncUtils.bookmarks.fetch(folderSep.syncId);
+    strictEqual(item.index, 1, "Should return separator position");
+  }
+
+  do_print("Fetch tag query");
+  {
+    let item = yield PlacesSyncUtils.bookmarks.fetch(tagQuery.syncId);
+    deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
+      "url", "title", "folder", "parentTitle"].sort(),
+      "Should include query-specific properties");
+    equal(item.url.href, `place:type=7&folder=${tagFolderId}`, "Should not rewrite outgoing tag queries");
+    equal(item.folder, "taggy", "Should return tag name for tag queries");
+  }
+
+  do_print("Fetch smart bookmark");
+  {
+    let item = yield PlacesSyncUtils.bookmarks.fetch(smartBmk.syncId);
+    deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
+      "url", "title", "query", "parentTitle"].sort(),
+      "Should include smart bookmark-specific properties");
+    equal(item.query, "BookmarksToolbar", "Should return query name for smart bookmarks");
+  }
+
+  yield PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(function* test_fetch_livemark() {
+  let { server, site, stopServer } = makeLivemarkServer();
+
+  try {
+    do_print("Create livemark");
+    let livemark = yield PlacesUtils.livemarks.addLivemark({
+      parentGuid: PlacesUtils.bookmarks.menuGuid,
+      feedURI: uri(site + "/feed/1"),
+      siteURI: uri(site),
+      index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+    });
+    PlacesUtils.annotations.setItemAnnotation(livemark.id, DESCRIPTION_ANNO,
+      "Livemark description", 0, PlacesUtils.annotations.EXPIRE_NEVER);
+
+    do_print("Fetch livemark");
+    let item = yield PlacesSyncUtils.bookmarks.fetch(livemark.guid);
+    deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
+      "description", "feed", "site", "parentTitle"].sort(),
+      "Should include livemark-specific properties");
+    equal(item.description, "Livemark description", "Should return description");
+    equal(item.feed.href, site + "/feed/1", "Should return feed URL");
+    equal(item.site.href, site + "/", "Should return site URL");
+  } finally {
+    yield stopServer();
+  }
+
+  yield PlacesUtils.bookmarks.eraseEverything();
+});
--- a/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
+++ b/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
@@ -10,16 +10,18 @@ const DEFAULT_CAPTURE_TIMEOUT = 30000; /
 const DESTROY_BROWSER_TIMEOUT = 60000; // ms
 const FRAME_SCRIPT_URL = "chrome://global/content/backgroundPageThumbsContent.js";
 
 const TELEMETRY_HISTOGRAM_ID_PREFIX = "FX_THUMBNAILS_BG_";
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
+const ABOUT_NEWTAB_SEGREGATION_PREF = "privacy.usercontext.about_newtab_segregation.enabled";
+
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 // possible FX_THUMBNAILS_BG_CAPTURE_DONE_REASON_2 telemetry values
@@ -191,20 +193,22 @@ const BackgroundPageThumbs = {
     if (this._thumbBrowser)
       return;
 
     let browser = this._parentWin.document.createElementNS(XUL_NS, "browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("remote", "true");
     browser.setAttribute("disableglobalhistory", "true");
 
-    // Use the private container for thumbnails.
-    let privateIdentity =
-      ContextualIdentityService.getPrivateIdentity("userContextIdInternal.thumbnail");
-    browser.setAttribute("usercontextid", privateIdentity.userContextId);
+    if (Services.prefs.getBoolPref(ABOUT_NEWTAB_SEGREGATION_PREF)) {
+      // Use the private container for thumbnails.
+      let privateIdentity =
+        ContextualIdentityService.getPrivateIdentity("userContextIdInternal.thumbnail");
+      browser.setAttribute("usercontextid", privateIdentity.userContextId);
+    }
 
     // Size the browser.  Make its aspect ratio the same as the canvases' that
     // the thumbnails are drawn into; the canvases' aspect ratio is the same as
     // the screen's, so use that.  Aim for a size in the ballpark of 1024x768.
     let [swidth, sheight] = [{}, {}];
     Cc["@mozilla.org/gfx/screenmanager;1"].
       getService(Ci.nsIScreenManager).
       primaryScreen.
@@ -430,21 +434,23 @@ Capture.prototype = {
         try {
           callback.call(options, this.url);
         }
         catch (err) {
           Cu.reportError(err);
         }
       }
 
-      // Clear the data in the private container for thumbnails.
-      let privateIdentity =
-        ContextualIdentityService.getPrivateIdentity("userContextIdInternal.thumbnail");
-      Services.obs.notifyObservers(null, "clear-origin-attributes-data",
+      if (Services.prefs.getBoolPref(ABOUT_NEWTAB_SEGREGATION_PREF)) {
+        // Clear the data in the private container for thumbnails.
+        let privateIdentity =
+          ContextualIdentityService.getPrivateIdentity("userContextIdInternal.thumbnail");
+        Services.obs.notifyObservers(null, "clear-origin-attributes-data",
           JSON.stringify({ userContextId: privateIdentity.userContextId }));
+      }
     };
 
     if (!data) {
       done();
       return;
     }
 
     PageThumbs._store(this.url, data.finalURL, data.imageData, true)
--- a/toolkit/components/url-classifier/RiceDeltaDecoder.cpp
+++ b/toolkit/components/url-classifier/RiceDeltaDecoder.cpp
@@ -20,29 +20,19 @@ namespace {
  *  project authors may be found in the AUTHORS file in the root of
  *  the source tree.
  */
 
 class BitBuffer {
  public:
   BitBuffer(const uint8_t* bytes, size_t byte_count);
 
-  // Gets the current offset, in bytes/bits, from the start of the buffer. The
-  // bit offset is the offset into the current byte, in the range [0,7].
-  void GetCurrentOffset(size_t* out_byte_offset, size_t* out_bit_offset);
-
   // The remaining bits in the byte buffer.
   uint64_t RemainingBitCount() const;
 
-  // Reads byte-sized values from the buffer. Returns false if there isn't
-  // enough data left for the specified type.
-  bool ReadUInt8(uint8_t* val);
-  bool ReadUInt16(uint16_t* val);
-  bool ReadUInt32(uint32_t* val);
-
   // Reads bit-sized values from the buffer. Returns false if there isn't enough
   // data left for the specified bit count..
   bool ReadBits(uint32_t* val, size_t bit_count);
 
   // Peeks bit-sized values from the buffer. Returns false if there isn't enough
   // data left for the specified number of bits. Doesn't move the current
   // offset.
   bool PeekBits(uint32_t* val, size_t bit_count);
@@ -51,32 +41,21 @@ class BitBuffer {
   // Exponential golomb values are encoded as:
   // 1) x = source val + 1
   // 2) In binary, write [countbits(x) - 1] 1s, then x
   // To decode, we count the number of leading 1 bits, read that many + 1 bits,
   // and increment the result by 1.
   // Returns false if there isn't enough data left for the specified type, or if
   // the value wouldn't fit in a uint32_t.
   bool ReadExponentialGolomb(uint32_t* val);
-  // Reads signed exponential golomb values at the current offset. Signed
-  // exponential golomb values are just the unsigned values mapped to the
-  // sequence 0, 1, -1, 2, -2, etc. in order.
-  bool ReadSignedExponentialGolomb(int32_t* val);
 
-  // Moves current position |byte_count| bytes forward. Returns false if
-  // there aren't enough bytes left in the buffer.
-  bool ConsumeBytes(size_t byte_count);
   // Moves current position |bit_count| bits forward. Returns false if
   // there aren't enough bits left in the buffer.
   bool ConsumeBits(size_t bit_count);
 
-  // Sets the current offset to the provied byte/bit offsets. The bit
-  // offset is from the given byte, in the range [0,7].
-  bool Seek(size_t byte_offset, size_t bit_offset);
-
  protected:
   const uint8_t* const bytes_;
   // The total size of |bytes_|.
   size_t byte_count_;
   // The current offset, in bytes, from the start of |bytes_|.
   size_t byte_offset_;
   // The current offset, in bits, into the current byte.
   size_t bit_offset_;
@@ -175,40 +154,16 @@ BitBuffer::BitBuffer(const uint8_t* byte
   MOZ_ASSERT(static_cast<uint64_t>(byte_count_) <=
              std::numeric_limits<uint32_t>::max());
 }
 
 uint64_t BitBuffer::RemainingBitCount() const {
   return (static_cast<uint64_t>(byte_count_) - byte_offset_) * 8 - bit_offset_;
 }
 
-bool BitBuffer::ReadUInt8(uint8_t* val) {
-  uint32_t bit_val;
-  if (!ReadBits(&bit_val, sizeof(uint8_t) * 8)) {
-    return false;
-  }
-  MOZ_ASSERT(bit_val <= std::numeric_limits<uint8_t>::max());
-  *val = static_cast<uint8_t>(bit_val);
-  return true;
-}
-
-bool BitBuffer::ReadUInt16(uint16_t* val) {
-  uint32_t bit_val;
-  if (!ReadBits(&bit_val, sizeof(uint16_t) * 8)) {
-    return false;
-  }
-  MOZ_ASSERT(bit_val <= std::numeric_limits<uint16_t>::max());
-  *val = static_cast<uint16_t>(bit_val);
-  return true;
-}
-
-bool BitBuffer::ReadUInt32(uint32_t* val) {
-  return ReadBits(val, sizeof(uint32_t) * 8);
-}
-
 bool BitBuffer::PeekBits(uint32_t* val, size_t bit_count) {
   if (!val || bit_count > RemainingBitCount() || bit_count > 32) {
     return false;
   }
   const uint8_t* bytes = bytes_ + byte_offset_;
   size_t remaining_bits_in_current_byte = 8 - bit_offset_;
   uint32_t bits = LowestBits(*bytes++, remaining_bits_in_current_byte);
   // If we're reading fewer bits than what's left in the current byte, just
@@ -233,20 +188,16 @@ bool BitBuffer::PeekBits(uint32_t* val, 
   *val = bits;
   return true;
 }
 
 bool BitBuffer::ReadBits(uint32_t* val, size_t bit_count) {
   return PeekBits(val, bit_count) && ConsumeBits(bit_count);
 }
 
-bool BitBuffer::ConsumeBytes(size_t byte_count) {
-  return ConsumeBits(byte_count * 8);
-}
-
 bool BitBuffer::ConsumeBits(size_t bit_count) {
   if (bit_count > RemainingBitCount()) {
     return false;
   }
 
   byte_offset_ += (bit_offset_ + bit_count) / 8;
   bit_offset_ = (bit_offset_ + bit_count) % 8;
   return true;
@@ -268,41 +219,9 @@ bool BitBuffer::ReadExponentialGolomb(ui
   }
   if (!ConsumeBits(1)) {
     return false; // The stream is incorrectly terminated at '1'.
   }
 
   *val = one_bit_count;
   return true;
 }
-
-bool BitBuffer::ReadSignedExponentialGolomb(int32_t* val) {
-  uint32_t unsigned_val;
-  if (!ReadExponentialGolomb(&unsigned_val)) {
-    return false;
-  }
-  if ((unsigned_val & 1) == 0) {
-    *val = -static_cast<int32_t>(unsigned_val / 2);
-  } else {
-    *val = (unsigned_val + 1) / 2;
-  }
-  return true;
 }
-
-void BitBuffer::GetCurrentOffset(
-    size_t* out_byte_offset, size_t* out_bit_offset) {
-  MOZ_ASSERT(out_byte_offset != NULL);
-  MOZ_ASSERT(out_bit_offset != NULL);
-  *out_byte_offset = byte_offset_;
-  *out_bit_offset = bit_offset_;
-}
-
-bool BitBuffer::Seek(size_t byte_offset, size_t bit_offset) {
-  if (byte_offset > byte_count_ || bit_offset > 7 ||
-      (byte_offset == byte_count_ && bit_offset > 0)) {
-    return false;
-  }
-  byte_offset_ = byte_offset;
-  bit_offset_ = bit_offset;
-  return true;
-}
-}
-
--- a/toolkit/components/url-classifier/content/listmanager.js
+++ b/toolkit/components/url-classifier/content/listmanager.js
@@ -191,18 +191,18 @@ PROT_ListManager.prototype.requireTableU
 
 /**
  * Acts as a nsIUrlClassifierCallback for getTables.
  */
 PROT_ListManager.prototype.kickoffUpdate_ = function (onDiskTableData)
 {
   this.startingUpdate_ = false;
   var initialUpdateDelay = 3000;
-  // Add a fuzz of 0-5 minutes.
-  initialUpdateDelay += Math.floor(Math.random() * (5 * 60 * 1000));
+  // Add a fuzz of 0-1 minutes for both v2 and v4 according to Bug 1305478.
+  initialUpdateDelay += Math.floor(Math.random() * (1 * 60 * 1000));
 
   // If the user has never downloaded tables, do the check now.
   log("needsUpdate: " + JSON.stringify(this.needsUpdate_, undefined, 2));
   for (var updateUrl in this.needsUpdate_) {
     // If we haven't already kicked off updates for this updateUrl, set a
     // non-repeating timer for it. The timer delay will be reset either on
     // updateSuccess to this.updateinterval, or backed off on downloadError.
     // Don't set the updateChecker unless at least one table has updates
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -26,17 +26,17 @@
     ContentTask.setTestScope(window.opener.wrappedJSObject);
 
     var gPrefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
     const SAMPLE_URL = "http://www.mozilla.org/";
     const SAMPLE_TEXT = "Some text in a text field.";
     const SEARCH_TEXT = "Text Test";
     const NOT_FOUND_TEXT = "This text is not on the page."
-    const ITERATOR_TIMEOUT = gPrefsvc.getIntPref("findbar.iteratorTimeout") + 20;
+    const ITERATOR_TIMEOUT = gPrefsvc.getIntPref("findbar.iteratorTimeout");
 
     var gFindBar = null;
     var gBrowser;
 
     var gClipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
     var gHasFindClipboard = gClipboard.supportsFindClipboard();
 
     var gStatusText;
@@ -250,17 +250,17 @@
         let listener = {
           onMatchesCountResult: function() {
             gFindBar.browser.finder.removeResultListener(listener);
             resolve();
           }
         };
         gFindBar.browser.finder.addResultListener(listener);
         // Make sure we resolve _at least_ after five times the find iterator timeout.
-        setTimeout(resolve, ITERATOR_TIMEOUT * 5);
+        setTimeout(resolve, (ITERATOR_TIMEOUT * 5) + 20);
       });
     }
 
     var enterStringIntoFindField = Task.async(function* (str, waitForResult = true) {
       for (let promise, i = 0; i < str.length; i++) {
         if (waitForResult) {
           promise = promiseFindResult();
         }
@@ -518,17 +518,17 @@
       ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
          "testFindCountUI: find field is not focused");
 
       let promise;
       let matchCase = gFindBar.getElement("find-case-sensitive");
       if (matchCase.checked) {
         promise = promiseFindResult();
         matchCase.click();
-        yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT));
+        yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20));
         yield promise;
       }
 
       let foundMatches = gFindBar._foundMatches;
       let tests = [{
         text: "t",
         current: 5,
         total: 10,
@@ -553,26 +553,32 @@
         is(aMatches[2], String(aTest.total),
           `Total amount of matches should be ${aTest.total} for '${aTest.text}'`);
       }
 
       for (let test of tests) {
         gFindBar._findField.select();
         gFindBar._findField.focus();
 
-        yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT));
+        let timeout = ITERATOR_TIMEOUT;
+        if (test.text.length == 1)
+          timeout *= 4;
+        else if (test.text.length == 2)
+          timeout *= 2;
+        timeout += 20;
+        yield new Promise(resolve => setTimeout(resolve, timeout));
         yield enterStringIntoFindField(test.text, false);
         yield promiseMatchesCountResult();
         let matches = foundMatches.value.match(regex);
         if (!test.total) {
           ok(!matches, "No message should be shown when 0 matches are expected");
         } else {
           assertMatches(test, matches);
           for (let i = 1; i < test.total; i++) {
-            yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT));
+            yield new Promise(resolve => setTimeout(resolve, timeout));
             gFindBar.onFindAgainCommand();
             yield promiseMatchesCountResult();
             // test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current
             let current = (test.current + i - 1) % test.total + 1;
             assertMatches({
               text: test.text,
               current: current,
               total: test.total
@@ -654,17 +660,17 @@
 
     function* testToggleEntireWord() {
       yield openFindbar();
       let promise = promiseFindResult();
       yield enterStringIntoFindField("Tex", false);
       let result = yield promise;
       is(result.result, Ci.nsITypeAheadFind.FIND_FOUND, "Text should be found");
 
-      yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT));
+      yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20));
       promise = promiseFindResult();
       let check = gFindBar.getElement("find-entire-word");
       check.click();
       result = yield promise;
       is(result.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "Text should NOT be found");
 
       check.click();
       gFindBar.close(true);
--- a/toolkit/modules/FinderIterator.jsm
+++ b/toolkit/modules/FinderIterator.jsm
@@ -165,16 +165,20 @@ this.FinderIterator = {
   stop(cachePrevious = false) {
     if (!this.running)
       return;
 
     if (this._timer) {
       clearTimeout(this._timer);
       this._timer = null;
     }
+    if (this._runningFindResolver) {
+      this._runningFindResolver();
+      this._runningFindResolver = null;
+    }
 
     if (cachePrevious) {
       this._previousRanges = [].concat(this.ranges);
       this._previousParams = Object.assign({}, this._currentParams);
     } else {
       this._previousRanges = [];
       this._previousParams = null;
     }
@@ -215,16 +219,20 @@ this.FinderIterator = {
    * previous result invalid.
    * If the iterator is running, it will be stopped as soon as possible.
    */
   reset() {
     if (this._timer) {
       clearTimeout(this._timer);
       this._timer = null;
     }
+    if (this._runningFindResolver) {
+      this._runningFindResolver();
+      this._runningFindResolver = null;
+    }
 
     this._catchingUp.clear();
     this._currentParams = this._previousParams = null;
     this._previousRanges = [];
     this.ranges = [];
     this.running = false;
 
     this._notifyListeners("reset");
@@ -416,18 +424,32 @@ this.FinderIterator = {
    *                               is not, this identifier is used to learn if
    *                               it's supposed to still continue after a pause.
    * @yield {nsIDOMRange}
    */
   _findAllRanges: Task.async(function* (finder, spawnId) {
     if (this._timeout) {
       if (this._timer)
         clearTimeout(this._timer);
-      yield new Promise(resolve => this._timer = setTimeout(resolve, this._timeout));
-      this._timer = null;
+      if (this._runningFindResolver)
+        this._runningFindResolver();
+
+      let timeout = this._timeout;
+      let searchTerm = this._currentParams.word;
+      // Wait a little longer when the first or second character is typed into
+      // the findbar.
+      if (searchTerm.length == 1)
+        timeout *= 4;
+      else if (searchTerm.length == 2)
+        timeout *= 2;
+      yield new Promise(resolve => {
+        this._runningFindResolver = resolve;
+        this._timer = setTimeout(resolve, timeout);
+      });
+      this._timer = this._runningFindResolver = null;
       // During the timeout, we could have gotten the signal to stop iterating.
       // Make sure we do here.
       if (!this.running || spawnId !== this._spawnId)
         return;
     }
 
     this._notifyListeners("start", this.params);
 
--- a/toolkit/modules/tests/browser/browser_FinderHighlighter.js
+++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js
@@ -216,17 +216,22 @@ add_task(function* testModalResults() {
   let url = kFixtureBaseURL + "file_FinderSample.html";
   yield BrowserTestUtils.withNewTab(url, function* (browser) {
     let findbar = gBrowser.getFindBar();
 
     for (let [word, expectedResult] of tests) {
       yield promiseOpenFindbar(findbar);
       Assert.ok(!findbar.hidden, "Findbar should be open now.");
 
-      yield new Promise(resolve => setTimeout(resolve, kIteratorTimeout));
+      let timeout = kIteratorTimeout;
+      if (word.length == 1)
+        timeout *= 4;
+      else if (word.length == 2)
+        timeout *= 2;
+      yield new Promise(resolve => setTimeout(resolve, timeout));
       let promise = promiseTestHighlighterOutput(browser, word, expectedResult,
         expectedResult.extraTest);
       yield promiseEnterStringIntoFindField(findbar, word);
       yield promise;
 
       findbar.close(true);
     }
   });
--- a/xpcom/base/nsTraceRefcnt.cpp
+++ b/xpcom/base/nsTraceRefcnt.cpp
@@ -569,32 +569,36 @@ LogThisType(const char* aTypeName)
 }
 
 static PLHashNumber
 HashNumber(const void* aKey)
 {
   return PLHashNumber(NS_PTR_TO_INT32(aKey));
 }
 
+// This method uses MOZ_RELEASE_ASSERT in the unlikely event that
+// somebody uses this in a non-debug build.
 static intptr_t
 GetSerialNumber(void* aPtr, bool aCreate)
 {
   PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers,
                                             HashNumber(aPtr),
                                             aPtr);
   if (hep && *hep) {
+    MOZ_RELEASE_ASSERT(!aCreate, "If an object already has a serial number, we should be destroying it.");
     return static_cast<SerialNumberRecord*>((*hep)->value)->serialNumber;
-  } else if (aCreate) {
-    SerialNumberRecord* record = new SerialNumberRecord();
-    WalkTheStackSavingLocations(record->allocationStack);
-    PL_HashTableRawAdd(gSerialNumbers, hep, HashNumber(aPtr),
-                       aPtr, static_cast<void*>(record));
-    return gNextSerialNumber;
   }
-  return 0;
+
+  MOZ_RELEASE_ASSERT(aCreate, "If an object does not have a serial number, we should be creating it.");
+
+  SerialNumberRecord* record = new SerialNumberRecord();
+  WalkTheStackSavingLocations(record->allocationStack);
+  PL_HashTableRawAdd(gSerialNumbers, hep, HashNumber(aPtr),
+                     aPtr, static_cast<void*>(record));
+  return gNextSerialNumber;
 }
 
 static int32_t*
 GetRefCount(void* aPtr)
 {
   PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers,
                                             HashNumber(aPtr),
                                             aPtr);