Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 25 Jul 2017 14:35:23 +0200
changeset 421942 8a193ce80a59d7a2062783efdf215c4c36fd8d6f
parent 421941 1f122ddfeedd498bbf4d3d452a68e0c17f35b789 (current diff)
parent 421904 07484bfdb96bc7297c404e377eea93f1d8ca4442 (diff)
child 421943 ca6f811e807ea7c4a2296de5838d4fa61b566d14
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone56.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 autoland
browser/base/content/test/plugins/browser_CTP_data_urls.js
browser/base/content/test/plugins/plugin_data_url.html
docshell/base/nsDocShell.cpp
--- a/addon-sdk/source/test/fixtures/native-addon-test/index.js
+++ b/addon-sdk/source/test/fixtures/native-addon-test/index.js
@@ -16,17 +16,22 @@ exports.customMainModule = require('test
 exports.customMainModuleRelative = require('test-custom-main-relative');
 exports.defaultMain = require('test-default-main');
 exports.testJSON = require('./dir/c');
 exports.dummyModule = require('./dir/dummy');
 
 exports.eventCore = require('sdk/event/core');
 exports.promise = require('sdk/core/promise');
 
-exports.localJSM  = require('./dir/test.jsm');
+if (module.uri.startsWith("file:"))
+  // We can't load the same file multiple times with different URLs, so
+  // skip this one.
+  exports.localJSM = { test: "this is a jsm" };
+else
+  exports.localJSM = require('./dir/test.jsm');
 exports.promisejsm = require('modules/Promise.jsm').Promise;
 exports.require = require;
 
 var math = require('test-math');
 exports.areModulesCached = (math === exports.math);
 
 // Added noise to test AST walker
 function square (x) {
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1100,30 +1100,32 @@ pref("security.sandbox.content.level", 1
 #endif
 #endif
 
 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is introduced as part of bug 742434, the naming is inspired from
 // its Windows/Mac counterpart, but on Linux it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "content sandbox using seccomp-bpf when available"
-// 2 -> "seccomp-bpf + file broker"
+// 2 -> "seccomp-bpf + write file broker"
+// 3 -> "seccomp-bpf + read/write file brokering"
 // Content sandboxing on Linux is currently in the stage of
 // 'just getting it enabled', which includes a very permissive whitelist. We
 // enable seccomp-bpf on nightly to see if everything is running, or if we need
 // to whitelist more system calls.
 //
 // So the purpose of this setting is to allow nightly users to disable the
 // sandbox while we fix their problems. This way, they won't have to wait for
 // another nightly release which disables seccomp-bpf again.
 //
 // This setting may not be required anymore once we decide to permanently
 // enable the content sandbox.
-pref("security.sandbox.content.level", 2);
+pref("security.sandbox.content.level", 3);
 pref("security.sandbox.content.write_path_whitelist", "");
+pref("security.sandbox.content.read_path_whitelist", "");
 pref("security.sandbox.content.syscall_whitelist", "");
 #endif
 
 #if defined(XP_MACOSX) || defined(XP_WIN)
 #if defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // ID (a UUID when set by gecko) that is used to form the name of a
 // sandbox-writable temporary directory to be used by content processes
 // when a temporary writable file is required in a level 1 sandbox.
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -17,17 +17,16 @@ support-files =
   plugin_both2.html
   plugin_bug744745.html
   plugin_bug749455.html
   plugin_bug787619.html
   plugin_bug797677.html
   plugin_bug820497.html
   plugin_clickToPlayAllow.html
   plugin_clickToPlayDeny.html
-  plugin_data_url.html
   plugin_favorfallback.html
   plugin_hidden_to_visible.html
   plugin_iframe.html
   plugin_outsideScrollArea.html
   plugin_overlayed.html
   plugin_positioned.html
   plugin_simple_blank.swf
   plugin_small.html
@@ -53,18 +52,16 @@ tags = blocklist
 [browser_clearplugindata.js]
 tags = blocklist
 [browser_CTP_context_menu.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3"   # fails intermittently on Linux (bug 909342)
 tags = blocklist
 [browser_CTP_crashreporting.js]
 skip-if = !crashreporter
 tags = blocklist
-[browser_CTP_data_urls.js]
-tags = blocklist
 [browser_CTP_drag_drop.js]
 tags = blocklist
 [browser_CTP_favorfallback.js]
 [browser_CTP_hide_overlay.js]
 tags = blocklist
 [browser_CTP_iframe.js]
 tags = blocklist
 [browser_CTP_multi_allow.js]
deleted file mode 100644
--- a/browser/base/content/test/plugins/browser_CTP_data_urls.js
+++ /dev/null
@@ -1,255 +0,0 @@
-var rootDir = getRootDirectory(gTestPath);
-const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
-var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
-var gTestBrowser = null;
-
-add_task(async function() {
-  registerCleanupFunction(function() {
-    clearAllPluginPermissions();
-    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
-    setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
-    Services.prefs.clearUserPref("plugins.click_to_play");
-    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-    gBrowser.removeCurrentTab();
-    window.focus();
-    gTestBrowser = null;
-  });
-
-  gBrowser.selectedTab =  BrowserTestUtils.addTab(gBrowser);
-  gTestBrowser = gBrowser.selectedBrowser;
-
-  Services.prefs.setBoolPref("plugins.click_to_play", true);
-  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
-
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
-  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
-});
-
-// Test that the click-to-play doorhanger still works when navigating to data URLs
-add_task(async function() {
-  await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
-
-  // Work around for delayed PluginBindingAttached
-  await promiseUpdatePluginBindings(gTestBrowser);
-
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 1a, Should have a click-to-play notification");
-
-  let pluginInfo = await promiseForPluginInfo("test");
-  ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
-
-  let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
-  await ContentTask.spawn(gTestBrowser, {}, async function() {
-    // navigate forward to a page with 'test' in it
-    content.document.getElementById("data-link-1").click();
-  });
-  await loadPromise;
-
-  // Work around for delayed PluginBindingAttached
-  await promiseUpdatePluginBindings(gTestBrowser);
-
-  popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Test 1b, Should have a click-to-play notification");
-
-  pluginInfo = await promiseForPluginInfo("test");
-  ok(!pluginInfo.activated, "Test 1b, plugin should not be activated");
-
-  let promise = promisePopupNotification("click-to-play-plugins");
-  await ContentTask.spawn(gTestBrowser, {}, async function() {
-    let plugin = content.document.getElementById("test");
-    let bounds = plugin.getBoundingClientRect();
-    let left = (bounds.left + bounds.right) / 2;
-    let top = (bounds.top + bounds.bottom) / 2;
-    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
-    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
-    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
-  });
-  await promise;
-
-  // Simulate clicking the "Allow Always" button.
-  let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
-    PopupNotifications.panel.firstChild;
-  await promiseForCondition(condition);
-  PopupNotifications.panel.firstChild._primaryButton.click();
-
-  // check plugin state
-  pluginInfo = await promiseForPluginInfo("test");
-  ok(pluginInfo.activated, "Test 1b, plugin should be activated");
-});
-
-// Test that the click-to-play notification doesn't break when navigating
-// to data URLs with multiple plugins.
-add_task(async function() {
-  // We click activated above
-  clearAllPluginPermissions();
-
-  await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
-
-  // Work around for delayed PluginBindingAttached
-  await promiseUpdatePluginBindings(gTestBrowser);
-
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 2a, Should have a click-to-play notification");
-
-  let pluginInfo = await promiseForPluginInfo("test");
-  ok(!pluginInfo.activated, "Test 2a, plugin should not be activated");
-
-  let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
-  await ContentTask.spawn(gTestBrowser, {}, async function() {
-    // navigate forward to a page with 'test1' & 'test2' in it
-    content.document.getElementById("data-link-2").click();
-  });
-  await loadPromise;
-
-  // Work around for delayed PluginBindingAttached
-  await ContentTask.spawn(gTestBrowser, {}, async function() {
-    content.document.getElementById("test1").clientTop;
-    content.document.getElementById("test2").clientTop;
-  });
-
-  pluginInfo = await promiseForPluginInfo("test1");
-  ok(!pluginInfo.activated, "Test 2a, test1 should not be activated");
-  pluginInfo = await promiseForPluginInfo("test2");
-  ok(!pluginInfo.activated, "Test 2a, test2 should not be activated");
-
-  notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 2b, Should have a click-to-play notification");
-
-  await promiseForNotificationShown(notification);
-
-  // Simulate choosing "Allow now" for the test plugin
-  is(notification.options.pluginData.size, 2, "Test 2b, Should have two types of plugin in the notification");
-
-  let centerAction = null;
-  for (let action of notification.options.pluginData.values()) {
-    if (action.pluginName == "Test") {
-      centerAction = action;
-      break;
-    }
-  }
-  ok(centerAction, "Test 2b, found center action for the Test plugin");
-
-  let centerItem = null;
-  for (let item of PopupNotifications.panel.firstChild.childNodes) {
-    is(item.value, "block", "Test 2b, all plugins should start out blocked");
-    if (item.action == centerAction) {
-      centerItem = item;
-      break;
-    }
-  }
-  ok(centerItem, "Test 2b, found center item for the Test plugin");
-
-  // "click" the button to activate the Test plugin
-  centerItem.value = "allownow";
-  PopupNotifications.panel.firstChild._primaryButton.click();
-
-  // Work around for delayed PluginBindingAttached
-  await promiseUpdatePluginBindings(gTestBrowser);
-
-  // check plugin state
-  pluginInfo = await promiseForPluginInfo("test1");
-  ok(pluginInfo.activated, "Test 2b, plugin should be activated");
-});
-
-add_task(async function() {
-  // We click activated above
-  clearAllPluginPermissions();
-
-  await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
-
-  // Work around for delayed PluginBindingAttached
-  await promiseUpdatePluginBindings(gTestBrowser);
-});
-
-// Test that when navigating to a data url, the plugin permission is inherited
-add_task(async function() {
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 3a, Should have a click-to-play notification");
-
-  // check plugin state
-  let pluginInfo = await promiseForPluginInfo("test");
-  ok(!pluginInfo.activated, "Test 3a, plugin should not be activated");
-
-  let promise = promisePopupNotification("click-to-play-plugins");
-  await ContentTask.spawn(gTestBrowser, {}, async function() {
-    let plugin = content.document.getElementById("test");
-    let bounds = plugin.getBoundingClientRect();
-    let left = (bounds.left + bounds.right) / 2;
-    let top = (bounds.top + bounds.bottom) / 2;
-    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
-    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
-    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
-  });
-  await promise;
-
-  // Simulate clicking the "Allow Always" button.
-  let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
-    PopupNotifications.panel.firstChild;
-  await promiseForCondition(condition);
-  PopupNotifications.panel.firstChild._primaryButton.click();
-
-  // check plugin state
-  pluginInfo = await promiseForPluginInfo("test");
-  ok(pluginInfo.activated, "Test 3a, plugin should be activated");
-
-  let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
-  await ContentTask.spawn(gTestBrowser, {}, async function() {
-    // navigate forward to a page with 'test' in it
-    content.document.getElementById("data-link-1").click();
-  });
-  await loadPromise;
-
-  // Work around for delayed PluginBindingAttached
-  await promiseUpdatePluginBindings(gTestBrowser);
-
-  // check plugin state
-  pluginInfo = await promiseForPluginInfo("test");
-  ok(pluginInfo.activated, "Test 3b, plugin should be activated");
-
-  clearAllPluginPermissions();
-});
-
-// Test that the click-to-play doorhanger still works
-// when directly navigating to data URLs.
-// Fails, bug XXX. Plugins plus a data url don't fire a load event.
-/*
-add_task(function* () {
-  yield promiseTabLoadEvent(gBrowser.selectedTab,
-   "data:text/html,Hi!<embed id='test' style='width:200px; height:200px' type='application/x-test'/>");
-
-  // Work around for delayed PluginBindingAttached
-  yield promiseUpdatePluginBindings(gTestBrowser);
-
-  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(notification, "Test 4a, Should have a click-to-play notification");
-
-  // check plugin state
-  let pluginInfo = yield promiseForPluginInfo("test");
-  ok(!pluginInfo.activated, "Test 4a, plugin should not be activated");
-
-  let promise = promisePopupNotification("click-to-play-plugins");
-  yield ContentTask.spawn(gTestBrowser, {}, function* () {
-    let plugin = content.document.getElementById("test");
-    let bounds = plugin.getBoundingClientRect();
-    let left = (bounds.left + bounds.right) / 2;
-    let top = (bounds.top + bounds.bottom) / 2;
-    let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
-    utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
-    utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
-  });
-  yield promise;
-
-  // Simulate clicking the "Allow Always" button.
-  let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
-    PopupNotifications.panel.firstChild;
-  yield promiseForCondition(condition);
-  PopupNotifications.panel.firstChild._primaryButton.click();
-
-  // check plugin state
-  pluginInfo = yield promiseForPluginInfo("test");
-  ok(pluginInfo.activated, "Test 4a, plugin should be activated");
-});
-*/
deleted file mode 100644
--- a/browser/base/content/test/plugins/plugin_data_url.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-<body>
-  <a id="data-link-1" href='data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>'>
-    data: with one plugin
-  </a><br />
-  <a id="data-link-2" href='data:text/html,<embed id="test1" style="width: 200px; height: 200px" type="application/x-test"/><embed id="test2" style="width: 200px; height: 200px" type="application/x-second-test"/>'>
-    data: with two plugins
-  </a><br />
-  <object id="test" style="width: 200px; height: 200px" type="application/x-test"></object>
-</body>
-</html>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1688,23 +1688,25 @@ file, You can obtain one at http://mozil
 
       <method name="enableOneOffSearches">
         <parameter name="enable"/>
         <body><![CDATA[
           this._oneOffSearchesEnabled = enable;
           if (enable) {
             this.oneOffSearchButtons.telemetryOrigin = "urlbar";
             this.oneOffSearchButtons.style.display = "-moz-box";
+            // Set .textbox first, since the popup setter will cause
+            // a _rebuild call that uses it.
+            this.oneOffSearchButtons.textbox = this.input;
             this.oneOffSearchButtons.popup = this;
-            this.oneOffSearchButtons.textbox = this.input;
           } else {
             this.oneOffSearchButtons.telemetryOrigin = null;
             this.oneOffSearchButtons.style.display = "none";
+            this.oneOffSearchButtons.textbox = null;
             this.oneOffSearchButtons.popup = null;
-            this.oneOffSearchButtons.textbox = null;
           }
         ]]></body>
       </method>
 
       <method name="openSearchSuggestionsNotificationLearnMoreURL">
         <body><![CDATA[
         let url = Services.urlFormatter.formatURL(
           Services.prefs.getCharPref("app.support.baseURL") + "suggestions"
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -97,18 +97,20 @@
         // Wait until the popupshowing event to avoid forcing immediate
         // attachment of the search-one-offs binding.
         this.textbox.popup.addEventListener("popupshowing", () => {
           let oneOffButtons = this.textbox.popup.oneOffButtons;
           // Some accessibility tests create their own <searchbar> that doesn't
           // use the popup binding below, so null-check oneOffButtons.
           if (oneOffButtons) {
             oneOffButtons.telemetryOrigin = "searchbar";
+            // Set .textbox first, since the popup setter will cause
+            // a _rebuild call that uses it.
+            oneOffButtons.textbox = this.textbox;
             oneOffButtons.popup = this.textbox.popup;
-            oneOffButtons.textbox = this.textbox;
           }
         }, {capturing: true, once: true});
       ]]></constructor>
 
       <destructor><![CDATA[
         this.destroy();
       ]]></destructor>
 
--- a/browser/themes/shared/incontentprefs-old/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs-old/preferences.inc.css
@@ -381,17 +381,17 @@ description > html|a {
  * Sync
  */
 
 #fxaProfileImage {
   max-width: 60px;
   border-radius: 50%;
   list-style-image: url(chrome://browser/skin/fxa/default-avatar.svg);
   margin-inline-end: 15px;
-  image-rendering: -moz-crisp-edges;
+  image-rendering: auto;
   border: 1px solid transparent;
 }
 
 #fxaLoginStatus[hasName] #fxaProfileImage {
   max-width: 80px;
 }
 
 #fxaProfileImage.actionable {
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -401,17 +401,17 @@ groupbox {
  * Sync
  */
 
 #fxaProfileImage {
   max-width: 60px;
   border-radius: 50%;
   list-style-image: url(chrome://browser/skin/fxa/default-avatar.svg);
   margin-inline-end: 15px;
-  image-rendering: -moz-crisp-edges;
+  image-rendering: auto;
   border: 1px solid transparent;
 }
 
 #fxaLoginStatus[hasName] #fxaProfileImage {
   max-width: 80px;
 }
 
 #fxaProfileImage.actionable {
--- a/devtools/client/debugger/new/debugger.css
+++ b/devtools/client/debugger/new/debugger.css
@@ -1311,17 +1311,18 @@ html[dir="rtl"] .managed-tree .tree .nod
 
 .source-footer .tab.active {
   color: var(--theme-body-color);
   background-color: var(--theme-body-background);
   border-color: var(--theme-splitter-color);
   border-top-color: transparent;
 }
 
-.source-footer .tab.active path, .source-footer .tab:hover path {
+.source-footer .tab.active path,
+.source-footer .tab:hover path {
   fill: var(--theme-body-color);
 }
 .outline-list {
   list-style-type: none;
   padding-left: 0px;
   width: 100%;
 }
 
--- a/devtools/client/debugger/new/debugger.js
+++ b/devtools/client/debugger/new/debugger.js
@@ -14037,16 +14037,20 @@ return /******/ (function(modules) { // 
 	  if (!source) {
 	    return;
 	  }
 
 	  return getSourceByURL(state, (0, _source2.getPrettySourceURL)(source.get("url")));
 	}
 
 	function getSourceByUrlInSources(sources, url) {
+	  if (!url) {
+	    return null;
+	  }
+
 	  return sources.find(source => source.get("url") === url);
 	}
 
 	function getSourceInSources(sources, id) {
 	  return sources.get(id);
 	}
 
 	var getSources = exports.getSources = (0, _reselect.createSelector)(getSourcesState, sources => sources.sources);
@@ -16715,17 +16719,17 @@ return /******/ (function(modules) { // 
 	 * @module actions/sources
 	 */
 
 	// If a request has been made to show this source, go ahead and
 	// select it.
 	function checkSelectedSource(state, dispatch, source) {
 	  var pendingLocation = (0, _selectors.getPendingSelectedLocation)(state);
 
-	  if (pendingLocation && pendingLocation.url === source.url) {
+	  if (pendingLocation && !!source.url && pendingLocation.url === source.url) {
 	    dispatch(selectSource(source.id, { line: pendingLocation.line }));
 	  }
 	}
 
 	function newSource(source) {
 	  return (() => {
 	    var _ref3 = _asyncToGenerator(function* (_ref4) {
 	      var dispatch = _ref4.dispatch,
@@ -21796,16 +21800,17 @@ return /******/ (function(modules) { // 
 	  domain: __webpack_require__(353),
 	  file: __webpack_require__(354),
 	  folder: __webpack_require__(355),
 	  globe: __webpack_require__(356),
 	  jquery: __webpack_require__(999),
 	  underscore: __webpack_require__(1117),
 	  lodash: __webpack_require__(1118),
 	  ember: __webpack_require__(1119),
+	  vuejs: __webpack_require__(1174),
 	  "magnifying-glass": __webpack_require__(357),
 	  "arrow-up": __webpack_require__(919),
 	  "arrow-down": __webpack_require__(920),
 	  pause: __webpack_require__(358),
 	  "pause-exceptions": __webpack_require__(359),
 	  plus: __webpack_require__(360),
 	  prettyPrint: __webpack_require__(361),
 	  react: __webpack_require__(1000),
@@ -24112,16 +24117,17 @@ return /******/ (function(modules) { // 
 	var CallSites = (0, _react.createFactory)(_CallSites3.default);
 
 	var cssVars = {
 	  searchbarHeight: "var(--editor-searchbar-height)",
 	  secondSearchbarHeight: "var(--editor-second-searchbar-height)",
 	  footerHeight: "var(--editor-footer-height)"
 	};
 
+	var debugExpression = void 0;
 	class Editor extends _react.PureComponent {
 
 	  constructor() {
 	    super();
 
 	    this.cbPanel = null;
 	    this.pendingJumpLine = null;
 	    this.lastJumpLine = null;
@@ -24144,17 +24150,16 @@ return /******/ (function(modules) { // 
 	  }
 
 	  componentWillReceiveProps(nextProps) {
 	    // This lifecycle method is responsible for updating the editor
 	    // text.
 	    var selectedSource = nextProps.selectedSource,
 	        selectedLocation = nextProps.selectedLocation;
 
-	    this.clearDebugLine(this.props.selectedFrame);
 
 	    if (nextProps.startPanelSize !== this.props.startPanelSize || nextProps.endPanelSize !== this.props.endPanelSize) {
 	      this.state.editor.codeMirror.setSize();
 	    }
 
 	    if (!selectedSource) {
 	      if (this.props.selectedSource) {
 	        this.showMessage("");
@@ -24162,24 +24167,25 @@ return /******/ (function(modules) { // 
 	    } else if (selectedSource.get("loading")) {
 	      this.showMessage(L10N.getStr("loadingText"));
 	    } else if (selectedSource.get("error")) {
 	      this.showMessage(selectedSource.get("error"));
 	    } else if (this.props.selectedSource !== selectedSource) {
 	      (0, _editor.showSourceText)(this.state.editor, selectedSource.toJS());
 	    }
 
-	    if (this.props.linesInScope !== nextProps.linesInScope) {
+	    if (this.state.editor && this.props.linesInScope !== nextProps.linesInScope) {
 	      this.state.editor.codeMirror.operation(() => {
 	        (0, _editor.clearLineClass)(this.state.editor.codeMirror, "in-scope");
 	      });
-	    }
-
-	    this.setDebugLine(nextProps.selectedFrame, selectedLocation);
-	    (0, _editor.resizeBreakpointGutter)(this.state.editor.codeMirror);
+
+	      this.clearDebugLine(this.props.selectedFrame);
+	      this.setDebugLine(nextProps.selectedFrame, selectedLocation);
+	      (0, _editor.resizeBreakpointGutter)(this.state.editor.codeMirror);
+	    }
 	  }
 
 	  setupEditor() {
 	    var editor = (0, _editor.createEditor)();
 
 	    // disables the default search shortcuts
 	    editor._initShortcuts = () => {};
 
@@ -24356,43 +24362,51 @@ return /******/ (function(modules) { // 
 	    var direction = e.shiftKey ? "prev" : "next";
 	    (0, _editor.traverseResults)(e, ctx, query, direction, searchModifiers.toJS());
 	  }
 
 	  clearPreviewSelection() {
 	    this.props.clearSelection();
 	  }
 
-	  openMenu(event, codeMirror) {
+	  inSelectedFrameSource() {
 	    var _props3 = this.props,
-	        selectedSource = _props3.selectedSource,
 	        selectedLocation = _props3.selectedLocation,
-	        showSource = _props3.showSource,
-	        jumpToMappedLocation = _props3.jumpToMappedLocation,
-	        addExpression = _props3.addExpression,
-	        toggleBlackBox = _props3.toggleBlackBox;
+	        selectedFrame = _props3.selectedFrame;
+
+	    return selectedFrame && selectedLocation && selectedFrame.location.sourceId == selectedLocation.sourceId;
+	  }
+
+	  openMenu(event, codeMirror) {
+	    var _props4 = this.props,
+	        selectedSource = _props4.selectedSource,
+	        selectedLocation = _props4.selectedLocation,
+	        showSource = _props4.showSource,
+	        jumpToMappedLocation = _props4.jumpToMappedLocation,
+	        addExpression = _props4.addExpression,
+	        toggleBlackBox = _props4.toggleBlackBox;
 
 
 	    return (0, _EditorMenu2.default)({
 	      codeMirror,
 	      event,
 	      selectedLocation,
 	      selectedSource,
 	      showSource,
 	      jumpToMappedLocation,
 	      addExpression,
 	      toggleBlackBox,
 	      onGutterContextMenu: this.onGutterContextMenu
 	    });
 	  }
 
 	  onGutterClick(cm, line, gutter, ev) {
-	    var _props4 = this.props,
-	        selectedSource = _props4.selectedSource,
-	        toggleBreakpoint = _props4.toggleBreakpoint;
+	    var _props5 = this.props,
+	        selectedSource = _props5.selectedSource,
+	        toggleBreakpoint = _props5.toggleBreakpoint;
 
 	    // ignore right clicks in the gutter
 
 	    if (ev.ctrlKey && ev.button === 0 || ev.which === 3 || selectedSource && selectedSource.get("isBlackBoxed")) {
 	      return;
 	    }
 
 	    if (this.isCbPanelOpen()) {
@@ -24400,21 +24414,21 @@ return /******/ (function(modules) { // 
 	    }
 
 	    if (gutter !== "CodeMirror-foldgutter") {
 	      toggleBreakpoint(line + 1);
 	    }
 	  }
 
 	  onGutterContextMenu(event) {
-	    var _props5 = this.props,
-	        selectedSource = _props5.selectedSource,
-	        breakpoints = _props5.breakpoints,
-	        toggleBreakpoint = _props5.toggleBreakpoint,
-	        toggleDisabledBreakpoint = _props5.toggleDisabledBreakpoint;
+	    var _props6 = this.props,
+	        selectedSource = _props6.selectedSource,
+	        breakpoints = _props6.breakpoints,
+	        toggleBreakpoint = _props6.toggleBreakpoint,
+	        toggleDisabledBreakpoint = _props6.toggleDisabledBreakpoint;
 
 
 	    if (selectedSource && selectedSource.get("isBlackBoxed")) {
 	      event.preventDefault();
 	      return;
 	    }
 
 	    var line = (0, _editor.lineAtHeight)(this.state.editor, event);
@@ -24441,20 +24455,20 @@ return /******/ (function(modules) { // 
 	    }
 	  }
 
 	  toggleConditionalPanel(line) {
 	    if (this.isCbPanelOpen()) {
 	      return this.closeConditionalPanel();
 	    }
 
-	    var _props6 = this.props,
-	        selectedLocation = _props6.selectedLocation,
-	        setBreakpointCondition = _props6.setBreakpointCondition,
-	        breakpoints = _props6.breakpoints;
+	    var _props7 = this.props,
+	        selectedLocation = _props7.selectedLocation,
+	        setBreakpointCondition = _props7.setBreakpointCondition,
+	        breakpoints = _props7.breakpoints;
 
 	    var sourceId = selectedLocation ? selectedLocation.sourceId : "";
 
 	    var breakpoint = breakpoints.find(bp => bp.location.line === line);
 	    var location = { sourceId, line };
 	    var condition = breakpoint ? breakpoint.condition : "";
 
 	    var panel = (0, _ConditionalPanel.renderConditionalPanel)({
@@ -24475,35 +24489,38 @@ return /******/ (function(modules) { // 
 	    this.cbPanel = null;
 	  }
 
 	  isCbPanelOpen() {
 	    return !!this.cbPanel;
 	  }
 
 	  clearDebugLine(selectedFrame) {
-	    if (selectedFrame) {
-	      var line = selectedFrame.location.line;
-	      if (this.debugExpression) {
-	        this.debugExpression.clear();
+	    if (this.state.editor && selectedFrame) {
+	      var _selectedFrame$locati = selectedFrame.location,
+	          sourceId = _selectedFrame$locati.sourceId,
+	          line = _selectedFrame$locati.line;
+
+	      if (debugExpression) {
+	        debugExpression.clear();
 	      }
 
 	      this.state.editor.codeMirror.removeLineClass(line - 1, "line", "new-debug-line");
 	    }
 	  }
 
 	  setDebugLine(selectedFrame, selectedLocation) {
-	    if (selectedFrame && selectedLocation && selectedFrame.location.sourceId === selectedLocation.sourceId) {
-	      var _selectedFrame$locati = selectedFrame.location,
-	          line = _selectedFrame$locati.line,
-	          column = _selectedFrame$locati.column;
+	    if (this.state.editor && selectedFrame && selectedLocation && selectedFrame.location.sourceId === selectedLocation.sourceId) {
+	      var _selectedFrame$locati2 = selectedFrame.location,
+	          line = _selectedFrame$locati2.line,
+	          column = _selectedFrame$locati2.column;
 
 	      this.state.editor.codeMirror.addLineClass(line - 1, "line", "new-debug-line");
 
-	      this.debugExpression = (0, _editor.markText)(this.state.editor, "debug-expression", {
+	      debugExpression = (0, _editor.markText)(this.state.editor, "debug-expression", {
 	        start: { line, column },
 	        end: { line, column: null }
 	      });
 	    }
 	  }
 
 	  // If the location has changed and a specific line is requested,
 	  // move to that line and flash it.
@@ -24536,47 +24553,16 @@ return /******/ (function(modules) { // 
 	  }
 
 	  showMessage(msg) {
 	    this.state.editor.replaceDocument(this.state.editor.createDocument());
 	    this.state.editor.setText(msg);
 	    this.state.editor.setMode({ name: "text" });
 	  }
 
-	  renderHighlightLines() {
-	    var highlightedLineRange = this.props.highlightedLineRange;
-
-
-	    if (!highlightedLineRange) {
-	      return;
-	    }
-
-	    return HighlightLines({
-	      editor: this.state.editor,
-	      highlightedLineRange
-	    });
-	  }
-
-	  renderHitCounts() {
-	    var _props7 = this.props,
-	        hitCount = _props7.hitCount,
-	        selectedSource = _props7.selectedSource;
-
-
-	    if (!selectedSource || selectedSource.get("loading") || !hitCount || !this.state.editor) {
-	      return;
-	    }
-
-	    return hitCount.filter(marker => marker.get("count") > 0).map(marker => HitMarker({
-	      key: marker.get("line"),
-	      hitData: marker.toJS(),
-	      editor: this.state.editor.codeMirror
-	    }));
-	  }
-
 	  getInlineEditorStyles() {
 	    var _props8 = this.props,
 	        selectedSource = _props8.selectedSource,
 	        horizontal = _props8.horizontal,
 	        searchOn = _props8.searchOn;
 
 
 	    var subtractions = [];
@@ -24590,20 +24576,51 @@ return /******/ (function(modules) { // 
 	      subtractions.push(cssVars.secondSearchbarHeight);
 	    }
 
 	    return {
 	      height: subtractions.length === 0 ? "100%" : `calc(100% - ${subtractions.join(" - ")})`
 	    };
 	  }
 
-	  renderPreview() {
+	  renderHighlightLines() {
+	    var highlightedLineRange = this.props.highlightedLineRange;
+
+
+	    if (!highlightedLineRange) {
+	      return;
+	    }
+
+	    return HighlightLines({
+	      editor: this.state.editor,
+	      highlightedLineRange
+	    });
+	  }
+
+	  renderHitCounts() {
 	    var _props9 = this.props,
-	        selectedSource = _props9.selectedSource,
-	        selection = _props9.selection;
+	        hitCount = _props9.hitCount,
+	        selectedSource = _props9.selectedSource;
+
+
+	    if (!selectedSource || selectedSource.get("loading") || !hitCount || !this.state.editor) {
+	      return;
+	    }
+
+	    return hitCount.filter(marker => marker.get("count") > 0).map(marker => HitMarker({
+	      key: marker.get("line"),
+	      hitData: marker.toJS(),
+	      editor: this.state.editor.codeMirror
+	    }));
+	  }
+
+	  renderPreview() {
+	    var _props10 = this.props,
+	        selectedSource = _props10.selectedSource,
+	        selection = _props10.selection;
 
 	    if (!this.state.editor || !selectedSource) {
 	      return null;
 	    }
 
 	    if (!selection || selection.updating) {
 	      return;
 	    }
@@ -24626,70 +24643,90 @@ return /******/ (function(modules) { // 
 	      popoverPos: cursorPos,
 	      onClose: () => this.clearPreviewSelection()
 	    });
 	  }
 
 	  renderInScopeLines() {
 	    var linesInScope = this.props.linesInScope;
 
-	    if (!(0, _devtoolsConfig.isEnabled)("highlightScopeLines") || !linesInScope || !this.inSelectedFrameSource()) {
+	    if (!this.state.editor || !(0, _devtoolsConfig.isEnabled)("highlightScopeLines") || !linesInScope || !this.inSelectedFrameSource()) {
 	      return;
 	    }
 
 	    this.state.editor.codeMirror.operation(() => {
 	      linesInScope.forEach(line => {
 	        this.state.editor.codeMirror.addLineClass(line - 1, "line", "in-scope");
 	      });
 	    });
 	  }
 
-	  inSelectedFrameSource() {
-	    var _props10 = this.props,
-	        selectedLocation = _props10.selectedLocation,
-	        selectedFrame = _props10.selectedFrame;
-
-	    return selectedFrame && selectedLocation && selectedFrame.location.sourceId == selectedLocation.sourceId;
-	  }
-
 	  renderCallSites() {
 	    var editor = this.state.editor;
 
 	    if (!editor || !(0, _devtoolsConfig.isEnabled)("columnBreakpoints")) {
 	      return null;
 	    }
 	    return CallSites({ editor });
 	  }
 
-	  render() {
+	  renderSearchBar() {
 	    var _props11 = this.props,
 	        selectSource = _props11.selectSource,
 	        selectedSource = _props11.selectedSource,
 	        highlightLineRange = _props11.highlightLineRange,
-	        clearHighlightLineRange = _props11.clearHighlightLineRange,
-	        coverageOn = _props11.coverageOn,
-	        pauseData = _props11.pauseData,
-	        horizontal = _props11.horizontal;
+	        clearHighlightLineRange = _props11.clearHighlightLineRange;
+
+
+	    if (!this.state.editor) {
+	      return null;
+	    }
+
+	    return SearchBar({
+	      editor: this.state.editor,
+	      selectSource,
+	      selectedSource,
+	      highlightLineRange,
+	      clearHighlightLineRange
+	    });
+	  }
+
+	  renderFooter() {
+	    var horizontal = this.props.horizontal;
+
+
+	    if (!this.state.editor) {
+	      return null;
+	    }
+	    return Footer({ editor: this.state.editor, horizontal });
+	  }
+
+	  renderBreakpoints() {
+	    if (!this.state.editor) {
+	      return null;
+	    }
+
+	    return Breakpoints({ editor: this.state.editor });
+	  }
+
+	  render() {
+	    var _props12 = this.props,
+	        coverageOn = _props12.coverageOn,
+	        pauseData = _props12.pauseData;
 
 
 	    return _react.DOM.div({
 	      className: (0, _classnames2.default)("editor-wrapper", {
 	        "coverage-on": coverageOn,
 	        paused: !!pauseData && (0, _devtoolsConfig.isEnabled)("highlightScopeLines")
 	      })
-	    }, SearchBar({
-	      editor: this.state.editor,
-	      selectSource,
-	      selectedSource,
-	      highlightLineRange,
-	      clearHighlightLineRange
-	    }), _react.DOM.div({
+	    }, this.renderSearchBar(), _react.DOM.div({
 	      className: "editor-mount devtools-monospace",
 	      style: this.getInlineEditorStyles()
-	    }), this.renderHighlightLines(), this.renderInScopeLines(), this.renderHitCounts(), Footer({ editor: this.state.editor, horizontal }), this.renderPreview(), this.renderCallSites(), Breakpoints({ editor: this.state.editor }));
+	    }), this.renderHighlightLines(), this.renderInScopeLines(), this.renderHitCounts(), this.renderFooter(), this.renderPreview(), this.renderCallSites(), this.renderBreakpoints());
 	  }
 	}
 
 	Editor.displayName = "Editor";
 
 	Editor.propTypes = {
 	  breakpoints: _reactImmutableProptypes2.default.map,
 	  hitCount: _react.PropTypes.object,
@@ -27814,35 +27851,31 @@ return /******/ (function(modules) { // 
 	        editor = _props2.editor,
 	        breakpoint = _props2.breakpoint,
 	        selectedSource = _props2.selectedSource;
 
 	    return editor !== nextProps.editor || breakpoint.disabled !== nextProps.breakpoint.disabled || breakpoint.condition !== nextProps.breakpoint.condition || breakpoint.loading !== nextProps.breakpoint.loading || selectedSource !== nextProps.selectedSource;
 	  }
 
 	  componentDidMount() {
-	    if (!this.props.editor) {
-	      return;
-	    }
-
 	    this.addBreakpoint();
 	  }
 
 	  componentDidUpdate() {
 	    this.addBreakpoint();
 	  }
 
 	  componentWillUnmount() {
 	    var _props3 = this.props,
 	        editor = _props3.editor,
 	        breakpoint = _props3.breakpoint,
 	        selectedSource = _props3.selectedSource;
 
 
-	    if (!editor || !selectedSource) {
+	    if (!selectedSource) {
 	      return;
 	    }
 
 	    if (breakpoint.loading) {
 	      return;
 	    }
 
 	    var line = breakpoint.location.line - 1;
@@ -27903,32 +27936,24 @@ return /******/ (function(modules) { // 
 	    this.props.editor.addLineClass(line, "line", "hit-marker");
 	  }
 
 	  shouldComponentUpdate(nextProps) {
 	    return this.props.editor !== nextProps.editor || this.props.hitData !== nextProps.hitData;
 	  }
 
 	  componentDidMount() {
-	    if (!this.props.editor) {
-	      return;
-	    }
-
 	    this.addMarker();
 	  }
 
 	  componentDidUpdate() {
 	    this.addMarker();
 	  }
 
 	  componentWillUnmount() {
-	    if (!this.props.editor) {
-	      return;
-	    }
-
 	    var hitData = this.props.hitData;
 	    var line = hitData.line - 1;
 
 	    this.props.editor.setGutterMarker(line, "hit-markers", null);
 	    this.props.editor.removeLineClass(line, "line", "hit-marker");
 	  }
 
 	  render() {
@@ -45928,17 +45953,18 @@ return /******/ (function(modules) { // 
 	 * numbers gutter. Editor CSS will absolutely position the gutter
 	 * beneath the line numbers. This makes it easy to be flexible with
 	 * how we overlay breakpoints.
 	 */
 	function resizeBreakpointGutter(editor) {
 	  var gutters = editor.display.gutters;
 	  var lineNumbers = gutters.querySelector(".CodeMirror-linenumbers");
 	  var breakpoints = gutters.querySelector(".breakpoints");
-	  breakpoints.style.width = `${lineNumbers.clientWidth}px`;
+	  var width = lineNumbers.clientWidth;
+	  breakpoints.style.width = `${width}px`;
 	}
 
 	module.exports = {
 	  removeLineClass,
 	  clearLineClass,
 	  getTextForLine,
 	  getCursorLine,
 	  resizeBreakpointGutter
@@ -46992,16 +47018,20 @@ return /******/ (function(modules) { // 
 	function isLodash(frame) {
 	  return getFrameUrl(frame).match(/lodash/i);
 	}
 
 	function isEmber(frame) {
 	  return getFrameUrl(frame).match(/ember/i);
 	}
 
+	function isVueJS(frame) {
+	  return getFrameUrl(frame).match(/vue\.js/i);
+	}
+
 	function isRxJs(frame) {
 	  return getFrameUrl(frame).match(/rxjs/i);
 	}
 
 	function isAngular(frame) {
 	  return getFrameUrl(frame).match(/angular/i);
 	}
 
@@ -47048,16 +47078,20 @@ return /******/ (function(modules) { // 
 	  if (isLodash(frame)) {
 	    return "Lodash";
 	  }
 
 	  if (isEmber(frame)) {
 	    return "Ember";
 	  }
 
+	  if (isVueJS(frame)) {
+	    return "VueJS";
+	  }
+
 	  if (isRxJs(frame)) {
 	    return "RxJS";
 	  }
 
 	  if (isAngular(frame)) {
 	    return "Angular";
 	  }
 	}
@@ -47070,16 +47104,19 @@ return /******/ (function(modules) { // 
 	  jQuery: {
 	    "jQuery.event.dispatch": "Dispatch Event"
 	  },
 	  React: {
 	    // eslint-disable-next-line max-len
 	    "ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext/renderedElement<": "Render",
 	    _renderValidatedComponentWithoutOwnerOrContext: "Render"
 	  },
+	  VueJS: {
+	    "renderMixin/Vue.prototype._render": "Render"
+	  },
 	  Webpack: {
 	    // eslint-disable-next-line camelcase
 	    __webpack_require__: "Bootstrap"
 	  }
 	};
 
 	function mapDisplayNames(frame, library) {
 	  var map = displayNameMap[library];
@@ -47376,20 +47413,16 @@ return /******/ (function(modules) { // 
 	class HighlightLines extends _react.Component {
 
 	  constructor() {
 	    super();
 	    this.highlightLineRange = this.highlightLineRange.bind(this);
 	  }
 
 	  componentDidMount() {
-	    if (!this.props.editor) {
-	      return;
-	    }
-
 	    this.highlightLineRange();
 	  }
 
 	  componentWillUpdate() {
 	    this.clearHighlightRange();
 	  }
 
 	  componentDidUpdate() {
@@ -47399,26 +47432,20 @@ return /******/ (function(modules) { // 
 	  componentWillUnmount() {
 	    this.clearHighlightRange();
 	  }
 
 	  clearHighlightRange() {
 	    var _props = this.props,
 	        highlightedLineRange = _props.highlightedLineRange,
 	        editor = _props.editor;
-
-
-	    if (!editor) {
-	      return;
-	    }
-
 	    var codeMirror = editor.codeMirror;
 
 
-	    if ((0, _isEmpty2.default)(highlightedLineRange) || !editor || !codeMirror) {
+	    if ((0, _isEmpty2.default)(highlightedLineRange) || !codeMirror) {
 	      return;
 	    }
 
 	    var start = highlightedLineRange.start,
 	        end = highlightedLineRange.end;
 
 	    codeMirror.operation(() => {
 	      (0, _range2.default)(start - 1, end).forEach(line => {
@@ -47426,22 +47453,16 @@ return /******/ (function(modules) { // 
 	      });
 	    });
 	  }
 
 	  highlightLineRange() {
 	    var _props2 = this.props,
 	        highlightedLineRange = _props2.highlightedLineRange,
 	        editor = _props2.editor;
-
-
-	    if (!editor) {
-	      return;
-	    }
-
 	    var codeMirror = editor.codeMirror;
 
 
 	    if ((0, _isEmpty2.default)(highlightedLineRange) || !codeMirror) {
 	      return;
 	    }
 
 	    var start = highlightedLineRange.start,
@@ -52305,22 +52326,18 @@ return /******/ (function(modules) { // 
 
 	  shouldComponentUpdate(nextProps) {
 	    return this.props.editor !== nextProps.editor;
 	  }
 
 	  componentDidMount() {
 	    var _props = this.props,
 	        breakpoint = _props.breakpoint,
-	        editor = _props.editor,
 	        showCallSite = _props.showCallSite;
 
-	    if (!editor) {
-	      return;
-	    }
 
 	    if (!breakpoint && !showCallSite) {
 	      return;
 	    }
 
 	    this.addCallSite();
 	  }
 
@@ -52344,17 +52361,17 @@ return /******/ (function(modules) { // 
 	        }
 	      } else if (!nextProps.breakpoint) {
 	        this.clearCallSite();
 	      }
 	    }
 	  }
 
 	  componentWillUnmount() {
-	    if (!this.props.editor || !this.marker) {
+	    if (!this.marker) {
 	      return;
 	    }
 	    this.marker.clear();
 	  }
 
 	  render() {
 	    return null;
 	  }
@@ -52467,16 +52484,17 @@ return /******/ (function(modules) { // 
 
 	    if (!prevProps.enabled && this.props.enabled) {
 	      this.updateResults(this.state.query);
 	    }
 	  }
 
 	  openSymbolModal(_, e) {
 	    e.preventDefault();
+	    e.stopPropagation();
 	    this.props.setActiveSearch("symbol");
 	  }
 
 	  onClick(e) {
 	    e.stopPropagation();
 	  }
 
 	  onChange(e) {
@@ -52488,16 +52506,17 @@ return /******/ (function(modules) { // 
 
 	    this.setState({ query: e.target.value });
 	    return this.updateResults(e.target.value);
 	  }
 
 	  closeModal() {
 	    this.props.closeActiveSearch();
 	    this.props.clearHighlightLineRange();
+	    this.setState({ query: "" });
 	  }
 
 	  selectResultItem(e, item) {
 	    var _props = this.props,
 	        selectSource = _props.selectSource,
 	        selectedSource = _props.selectedSource;
 
 
@@ -52693,12 +52712,20 @@ return /******/ (function(modules) { // 
 	}, dispatch => (0, _redux.bindActionCreators)(_actions2.default, dispatch))(SymbolModal);
 
 /***/ },
 /* 1171 */
 /***/ function(module, exports) {
 
 	// removed by extract-text-webpack-plugin
 
+/***/ },
+/* 1172 */,
+/* 1173 */,
+/* 1174 */
+/***/ function(module, exports) {
+
+	module.exports = "<svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 400\" xml:space=\"preserve\" id=\"svg2\" version=\"1.1\"><metadata id=\"metadata8\"><rdf:RDF><cc:Work rdf:about><dc:format>image/svg+xml</dc:format><dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\"></dc></cc:Work></rdf:RDF></metadata><defs id=\"defs6\"></defs><g transform=\"matrix(1.3333333,0,0,-1.3333333,0,400)\" id=\"g10\"><g transform=\"translate(178.0626,235.0086)\" id=\"g12\"><path id=\"path14\" style=\"fill:#41b883;fill-opacity:1;fill-rule:nonzero;stroke:none\" d=\"M 0,0 -22.669,-39.264 -45.338,0 h -75.491 L -22.669,-170.017 75.491,0 Z\"></path></g><g transform=\"translate(178.0626,235.0086)\" id=\"g16\"><path id=\"path18\" style=\"fill:#34495e;fill-opacity:1;fill-rule:nonzero;stroke:none\" d=\"M 0,0 -22.669,-39.264 -45.338,0 H -81.565 L -22.669,-102.01 36.227,0 Z\"></path></g></g></svg>"
+
 /***/ }
 /******/ ])
 });
 ;
\ No newline at end of file
--- a/devtools/client/debugger/new/parser-worker.js
+++ b/devtools/client/debugger/new/parser-worker.js
@@ -30884,17 +30884,17 @@ return /******/ (function(modules) { // 
 
 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 	var ASTs = new Map();
 
 	function _parse(code, opts) {
 	  return babylon.parse(code, Object.assign({}, opts, {
 	    sourceType: "module",
-	    plugins: ["jsx", "flow"]
+	    plugins: ["jsx", "flow", "objectRestSpread"]
 	  }));
 	}
 
 	function parse(text, opts) {
 	  var ast = void 0;
 	  if (!text) {
 	    return;
 	  }
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -47,16 +47,17 @@ support-files =
 [browser_dbg-breakpoints.js]
 [browser_dbg-breakpoints-reloading.js]
 [browser_dbg-breakpoints-cond.js]
 [browser_dbg-call-stack.js]
 [browser_dbg-expressions.js]
 [browser_dbg-scopes.js]
 [browser_dbg-chrome-create.js]
 [browser_dbg-chrome-debugging.js]
+skip-if = debug # bug 1374187
 [browser_dbg-console.js]
 [browser_dbg-debugger-buttons.js]
 [browser_dbg-editor-gutter.js]
 [browser_dbg-editor-select.js]
 [browser_dbg-editor-highlight.js]
 [browser_dbg-iframes.js]
 [browser_dbg_keyboard_navigation.js]
 [browser_dbg_keyboard-shortcuts.js]
--- a/devtools/client/inspector/layout/components/App.js
+++ b/devtools/client/inspector/layout/components/App.js
@@ -35,17 +35,16 @@ const App = createClass({
 
   propTypes: {
     boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired,
     getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired,
     highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings).isRequired,
     setSelectedNode: PropTypes.func.isRequired,
     showBoxModelProperties: PropTypes.bool.isRequired,
-    showGridOutline: PropTypes.bool.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onPromoteLearnMoreClick: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelEditor: PropTypes.func.isRequired,
     onShowBoxModelHighlighter: PropTypes.func.isRequired,
     onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -21,16 +21,17 @@ const ClassListPreviewer = require("devt
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 const {
   VIEW_NODE_SELECTOR_TYPE,
   VIEW_NODE_PROPERTY_TYPE,
   VIEW_NODE_VALUE_TYPE,
   VIEW_NODE_IMAGE_URL_TYPE,
   VIEW_NODE_LOCATION_TYPE,
+  VIEW_NODE_SHAPE_POINT_TYPE,
 } = require("devtools/client/inspector/shared/node-types");
 const StyleInspectorMenu = require("devtools/client/inspector/shared/style-inspector-menu");
 const TooltipsOverlay = require("devtools/client/inspector/shared/tooltips-overlay");
 const {createChild, promiseWarn, debounce} = require("devtools/client/inspector/shared/utils");
 const EventEmitter = require("devtools/shared/event-emitter");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
@@ -43,16 +44,17 @@ const PREF_ENABLE_MDN_DOCS_TOOLTIP =
 const FILTER_CHANGED_TIMEOUT = 150;
 const PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
 
 // This is used to parse user input when filtering.
 const FILTER_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*;?$/;
 // This is used to parse the filter search value to see if the filter
 // should be strict or not
 const FILTER_STRICT_RE = /\s*`(.*?)`\s*$/;
+const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
 
 /**
  * Our model looks like this:
  *
  * ElementStyle:
  *   Responsible for keeping track of which properties are overridden.
  *   Maintains a list of Rule objects that apply to the element.
  * Rule:
@@ -328,16 +330,29 @@ CssRuleView.prototype = {
         property: getPropertyNameAndValue(node).name,
         value: node.textContent,
         enabled: prop.enabled,
         overridden: prop.overridden,
         pseudoElement: prop.rule.pseudoElement,
         sheetHref: prop.rule.domRule.href,
         textProperty: prop
       };
+    } else if (classes.contains("ruleview-shape-point") && prop) {
+      type = VIEW_NODE_SHAPE_POINT_TYPE;
+      value = {
+        property: getPropertyNameAndValue(node).name,
+        value: node.textContent,
+        enabled: prop.enabled,
+        overridden: prop.overridden,
+        pseudoElement: prop.rule.pseudoElement,
+        sheetHref: prop.rule.domRule.href,
+        textProperty: prop,
+        toggleActive: getShapeToggleActive(node),
+        point: getShapePoint(node)
+      };
     } else if (classes.contains("theme-link") &&
                !classes.contains("ruleview-rule-source") && prop) {
       type = VIEW_NODE_IMAGE_URL_TYPE;
       value = {
         property: getPropertyNameAndValue(node).name,
         value: node.parentNode.textContent,
         url: node.href,
         enabled: prop.enabled,
@@ -1534,16 +1549,62 @@ function getPropertyNameAndValue(node) {
         name: node.querySelector(".ruleview-propertyname").textContent,
         value: node.querySelector(".ruleview-propertyvalue").textContent
       };
     }
     node = node.parentNode;
   }
 }
 
+/**
+ * Walk up the DOM from a given node until a parent property holder is found,
+ * and return an active shape toggle if one exists.
+ *
+ * @param {DOMNode} node
+ *        The node to start from
+ * @returns {DOMNode} The active shape toggle node, if one exists.
+ */
+function getShapeToggleActive(node) {
+  while (true) {
+    if (!node || !node.classList) {
+      return null;
+    }
+    // Check first for ruleview-computed since it's the deepest
+    if (node.classList.contains("ruleview-computed") ||
+        node.classList.contains("ruleview-property")) {
+      return node.querySelector(".ruleview-shape.active");
+    }
+    node = node.parentNode;
+  }
+}
+
+/**
+ * Get the point associated with a shape point node.
+ *
+ * @param {DOMNode} node
+ *        A shape point node
+ * @returns {String} The point associated with the given node.
+ */
+function getShapePoint(node) {
+  let classList = node.classList;
+  let point = node.dataset.point;
+  // Inset points use classes instead of data because a single span can represent
+  // multiple points.
+  let insetClasses = [];
+  classList.forEach(className => {
+    if (INSET_POINT_TYPES.includes(className)) {
+      insetClasses.push(className);
+    }
+  });
+  if (insetClasses.length > 0) {
+    point = insetClasses.join(",");
+  }
+  return point;
+}
+
 function RuleViewTool(inspector, window) {
   this.inspector = inspector;
   this.document = window.document;
 
   this.view = new CssRuleView(this.inspector, this.document);
 
   this.clearUserProperties = this.clearUserProperties.bind(this);
   this.refresh = this.refresh.bind(this);
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -228,16 +228,22 @@ subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_selector-highlighter-on-navigate.js]
 [browser_rules_selector-highlighter_01.js]
 [browser_rules_selector-highlighter_02.js]
 [browser_rules_selector-highlighter_03.js]
 [browser_rules_selector-highlighter_04.js]
 [browser_rules_selector-highlighter_05.js]
 [browser_rules_selector_highlight.js]
+[browser_rules_shapes-toggle_01.js]
+[browser_rules_shapes-toggle_02.js]
+[browser_rules_shapes-toggle_03.js]
+[browser_rules_shapes-toggle_04.js]
+[browser_rules_shapes-toggle_05.js]
+[browser_rules_shapes-toggle_06.js]
 [browser_rules_shorthand-overridden-lists.js]
 [browser_rules_strict-search-filter-computed-list_01.js]
 [browser_rules_strict-search-filter_01.js]
 [browser_rules_strict-search-filter_02.js]
 [browser_rules_strict-search-filter_03.js]
 [browser_rules_style-editor-link.js]
 skip-if = !debug # Bug 1309759
 [browser_rules_url-click-opens-new-tab.js]
--- a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-restored-after-reload.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-restored-after-reload.js
@@ -45,24 +45,24 @@ add_task(function* () {
   info("Toggling ON the CSS grid highlighter from the rule-view.");
   let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   yield onHighlighterShown;
 
   ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
 
   info("Reload the page, expect the highlighter to be displayed once again");
-  let onStateRestored = highlighters.once("state-restored");
+  let onStateRestored = highlighters.once("grid-state-restored");
   yield refreshTab(gBrowser.selectedTab);
   let { restored } = yield onStateRestored;
   ok(restored, "The highlighter state was restored");
 
   info("Check that the grid highlighter can be displayed after reloading the page");
   ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
 
   info("Navigate to another URL, and check that the highlighter is hidden");
   let otherUri = "data:text/html;charset=utf-8," + encodeURIComponent(OTHER_URI);
-  onStateRestored = highlighters.once("state-restored");
+  onStateRestored = highlighters.once("grid-state-restored");
   yield navigateTo(inspector, otherUri);
   ({ restored } = yield onStateRestored);
   ok(!restored, "The highlighter state was not restored");
   ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shapes-toggle_01.js
@@ -0,0 +1,63 @@
+/* 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";
+
+// Test toggling the shapes highlighter in the rule view and the display of the
+// shapes highlighter.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #shape {
+      width: 800px;
+      height: 800px;
+      clip-path: circle(25%);
+    }
+  </style>
+  <div id="shape"></div>
+`;
+
+const HIGHLIGHTER_TYPE = "ShapesHighlighter";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  yield selectNode("#shape", inspector);
+  let container = getRuleViewProperty(view, "#shape", "clip-path").valueSpan;
+  let shapesToggle = container.querySelector(".ruleview-shape");
+
+  info("Checking the initial state of the CSS shape toggle in the rule-view.");
+  ok(shapesToggle, "Shapes highlighter toggle is visible.");
+  ok(!shapesToggle.classList.contains("active"),
+    "Shapes highlighter toggle button is not active.");
+  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "No CSS shapes highlighter exists in the rule-view.");
+  ok(!highlighters.shapesHighlighterShown, "No CSS shapes highlighter is shown.");
+
+  info("Toggling ON the CSS shapes highlighter from the rule-view.");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  shapesToggle.click();
+  yield onHighlighterShown;
+
+  info("Checking the CSS shapes highlighter is created and toggle button is active in " +
+    "the rule-view.");
+  ok(shapesToggle.classList.contains("active"),
+    "Shapes highlighter toggle is active.");
+  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "CSS shapes highlighter created in the rule-view.");
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is shown.");
+
+  info("Toggling OFF the CSS shapes highlighter from the rule-view.");
+  let onHighlighterHidden = highlighters.once("shapes-highlighter-hidden");
+  shapesToggle.click();
+  yield onHighlighterHidden;
+
+  info("Checking the CSS shapes highlighter is not shown and toggle button is not " +
+    "active in the rule-view.");
+  ok(!shapesToggle.classList.contains("active"),
+    "shapes highlighter toggle button is not active.");
+  ok(!highlighters.shapesHighlighterShown, "No CSS shapes highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shapes-toggle_02.js
@@ -0,0 +1,72 @@
+/* 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";
+
+// Test toggling the shapes highlighter in the rule view from an overridden
+// declaration.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #shape {
+      width: 800px;
+      height: 800px;
+      clip-path: circle(25%);
+    }
+    div {
+      clip-path: circle(30%);
+    }
+  </style>
+  <div id="shape"></div>
+`;
+
+const HIGHLIGHTER_TYPE = "ShapesHighlighter";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  yield selectNode("#shape", inspector);
+  let container = getRuleViewProperty(view, "#shape", "clip-path").valueSpan;
+  let shapeToggle = container.querySelector(".ruleview-shape");
+  let overriddenContainer = getRuleViewProperty(view, "div", "clip-path").valueSpan;
+  let overriddenShapeToggle = overriddenContainer.querySelector(".ruleview-shape");
+
+  info("Checking the initial state of the CSS shapes toggle in the rule-view.");
+  ok(shapeToggle && overriddenShapeToggle, "Shapes highlighter toggles are visible.");
+  ok(!shapeToggle.classList.contains("active") &&
+    !overriddenShapeToggle.classList.contains("active"),
+    "Shapes highlighter toggle buttons are not active.");
+  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "No CSS shapes highlighter exists in the rule-view.");
+  ok(!highlighters.shapesHighlighterShown, "No CSS shapes highlighter is shown.");
+
+  info("Toggling ON the shapes highlighter from the overridden rule in the rule-view.");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  overriddenShapeToggle.click();
+  yield onHighlighterShown;
+
+  info("Checking the shapes highlighter is created and toggle buttons are active in " +
+    "the rule-view.");
+  ok(shapeToggle.classList.contains("active") &&
+    overriddenShapeToggle.classList.contains("active"),
+    "shapes highlighter toggle is active.");
+  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "CSS shapes highlighter created in the rule-view.");
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is shown.");
+
+  info("Toggling off the shapes highlighter from the normal shapes declaration in the " +
+    "rule-view.");
+  let onHighlighterHidden = highlighters.once("shapes-highlighter-hidden");
+  shapeToggle.click();
+  yield onHighlighterHidden;
+
+  info("Checking the CSS shapes highlighter is not shown and toggle buttons are not " +
+    "active in the rule-view.");
+  ok(!shapeToggle.classList.contains("active") &&
+    !overriddenShapeToggle.classList.contains("active"),
+    "shapes highlighter toggle buttons are not active.");
+  ok(!highlighters.shapesHighlighterShown, "No CSS shapes highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shapes-toggle_03.js
@@ -0,0 +1,92 @@
+/* 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";
+
+// Test toggling the shapes highlighter in the rule view with multiple shapes in the page.
+
+const TEST_URI = `
+  <style type='text/css'>
+    .shape {
+      width: 800px;
+      height: 800px;
+      clip-path: circle(25%);
+    }
+  </style>
+  <div class="shape" id="shape1"></div>
+  <div class="shape" id="shape2"></div>
+`;
+
+const HIGHLIGHTER_TYPE = "ShapesHighlighter";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  info("Selecting the first shape container.");
+  yield selectNode("#shape1", inspector);
+  let container = getRuleViewProperty(view, ".shape", "clip-path").valueSpan;
+  let shapeToggle = container.querySelector(".ruleview-shape");
+
+  info("Checking the state of the CSS shape toggle for the first shape container " +
+    "in the rule-view.");
+  ok(shapeToggle, "shape highlighter toggle is visible.");
+  ok(!shapeToggle.classList.contains("active"),
+    "shape highlighter toggle button is not active.");
+  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "No CSS shape highlighter exists in the rule-view.");
+  ok(!highlighters.shapesHighlighterShown, "No CSS shapes highlighter is shown.");
+
+  info("Toggling ON the CSS shapes highlighter for the first shapes container from the " +
+    "rule-view.");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  shapeToggle.click();
+  yield onHighlighterShown;
+
+  info("Checking the CSS shapes highlighter is created and toggle button is active in " +
+    "the rule-view.");
+  ok(shapeToggle.classList.contains("active"),
+    "shapes highlighter toggle is active.");
+  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "CSS shapes highlighter created in the rule-view.");
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is shown.");
+
+  info("Selecting the second shapes container.");
+  yield selectNode("#shape2", inspector);
+  let firstShapesHighlighterShown = highlighters.shapesHighlighterShown;
+  container = getRuleViewProperty(view, ".shape", "clip-path").valueSpan;
+  shapeToggle = container.querySelector(".ruleview-shape");
+
+  info("Checking the state of the CSS shapes toggle for the second shapes container " +
+    "in the rule-view.");
+  ok(shapeToggle, "shapes highlighter toggle is visible.");
+  ok(!shapeToggle.classList.contains("active"),
+    "shapes highlighter toggle button is not active.");
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is still shown.");
+
+  info("Toggling ON the CSS shapes highlighter for the second shapes container " +
+    "from the rule-view.");
+  onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  shapeToggle.click();
+  yield onHighlighterShown;
+
+  info("Checking the CSS shapes highlighter is created for the second shapes container " +
+    "and toggle button is active in the rule-view.");
+  ok(shapeToggle.classList.contains("active"),
+    "shapes highlighter toggle is active.");
+  ok(highlighters.shapesHighlighterShown != firstShapesHighlighterShown,
+    "shapes highlighter for the second shapes container is shown.");
+
+  info("Selecting the first shapes container.");
+  yield selectNode("#shape1", inspector);
+  container = getRuleViewProperty(view, ".shape", "clip-path").valueSpan;
+  shapeToggle = container.querySelector(".ruleview-shape");
+
+  info("Checking the state of the CSS shapes toggle for the first shapes container " +
+    "in the rule-view.");
+  ok(shapeToggle, "shapes highlighter toggle is visible.");
+  ok(!shapeToggle.classList.contains("active"),
+    "shapes highlighter toggle button is not active.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shapes-toggle_04.js
@@ -0,0 +1,46 @@
+/* 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";
+
+// Test toggling the shapes highlighter in the rule view and modifying the 'clip-path'
+// declaration.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #shape {
+      width: 800px;
+      height: 800px;
+      clip-path: circle(25%);
+    }
+  </style>
+  <div id="shape"></div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  yield selectNode("#shape", inspector);
+  let container = getRuleViewProperty(view, "#shape", "clip-path").valueSpan;
+  let shapeToggle = container.querySelector(".ruleview-shape");
+
+  info("Toggling ON the CSS shape highlighter from the rule-view.");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  shapeToggle.click();
+  yield onHighlighterShown;
+
+  info("Edit the clip-path property to ellipse.");
+  let editor = yield focusEditableField(view, container, 30);
+  let onDone = view.once("ruleview-changed");
+  editor.input.value = "ellipse(30% 20%);";
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onDone;
+
+  info("Check the shape highlighter and shape toggle button are still visible.");
+  shapeToggle = container.querySelector(".ruleview-shape");
+  ok(shapeToggle, "Shape highlighter toggle is visible.");
+  ok(highlighters.shapesHighlighterShown, "CSS shape highlighter is shown.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shapes-toggle_05.js
@@ -0,0 +1,43 @@
+/* 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";
+
+// Tests that the shapes highlighter is hidden when the highlighted shape container is
+// removed from the page.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #shape {
+      width: 800px;
+      height: 800px;
+      clip-path: circle(25%);
+    }
+  </style>
+  <div id="shape"></div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view, testActor} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  yield selectNode("#shape", inspector);
+  let container = getRuleViewProperty(view, "#shape", "clip-path").valueSpan;
+  let shapeToggle = container.querySelector(".ruleview-shape");
+
+  info("Toggling ON the CSS shapes highlighter from the rule-view.");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  shapeToggle.click();
+  yield onHighlighterShown;
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is shown.");
+
+  let onHighlighterHidden = highlighters.once("shapes-highlighter-hidden");
+  info("Remove the #shapes container in the content page");
+  testActor.eval(`
+    content.document.querySelector("#shape").remove();
+  `);
+  yield onHighlighterHidden;
+  ok(!highlighters.shapesHighlighterShown, "CSS shapes highlighter is hidden.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shapes-toggle_06.js
@@ -0,0 +1,78 @@
+/* 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";
+
+// Test toggling the shapes highlighter in the rule view with clip-path and shape-outside
+// on the same element.
+
+const TEST_URI = `
+  <style type='text/css'>
+    .shape {
+      width: 800px;
+      height: 800px;
+      clip-path: circle(25%);
+      shape-outside: circle(25%);
+    }
+  </style>
+  <div class="shape" id="shape1"></div>
+  <div class="shape" id="shape2"></div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  yield selectNode("#shape1", inspector);
+  let clipPathContainer = getRuleViewProperty(view, ".shape", "clip-path").valueSpan;
+  let clipPathShapeToggle = clipPathContainer.querySelector(".ruleview-shape");
+  let shapeOutsideContainer = getRuleViewProperty(view, ".shape",
+    "shape-outside").valueSpan;
+  let shapeOutsideToggle = shapeOutsideContainer.querySelector(".ruleview-shape");
+
+  info("Toggling ON the CSS shapes highlighter for clip-path from the rule-view.");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  clipPathShapeToggle.click();
+  yield onHighlighterShown;
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is shown.");
+  ok(clipPathShapeToggle.classList.contains("active"),
+     "clip-path toggle button is active.");
+  ok(!shapeOutsideToggle.classList.contains("active"),
+     "shape-outside toggle button is not active.");
+
+  info("Toggling ON the CSS shapes highlighter for shape-outside from the rule-view.");
+  onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  shapeOutsideToggle.click();
+  yield onHighlighterShown;
+  ok(highlighters.shapesHighlighterShown, "CSS shapes highlighter is shown.");
+  ok(!clipPathShapeToggle.classList.contains("active"),
+     "clip-path toggle button is not active.");
+  ok(shapeOutsideToggle.classList.contains("active"),
+     "shape-outside toggle button is active.");
+
+  info("Selecting the second shapes container.");
+  yield selectNode("#shape2", inspector);
+  clipPathContainer = getRuleViewProperty(view, ".shape", "clip-path").valueSpan;
+  clipPathShapeToggle = clipPathContainer.querySelector(".ruleview-shape");
+  shapeOutsideContainer = getRuleViewProperty(view, ".shape",
+    "shape-outside").valueSpan;
+  shapeOutsideToggle = shapeOutsideContainer.querySelector(".ruleview-shape");
+  ok(!clipPathShapeToggle.classList.contains("active"),
+     "clip-path toggle button is not active.");
+  ok(!shapeOutsideToggle.classList.contains("active"),
+     "shape-outside toggle button is not active.");
+
+  info("Selecting the first shapes container.");
+  yield selectNode("#shape1", inspector);
+  clipPathContainer = getRuleViewProperty(view, ".shape", "clip-path").valueSpan;
+  clipPathShapeToggle = clipPathContainer.querySelector(".ruleview-shape");
+  shapeOutsideContainer = getRuleViewProperty(view, ".shape",
+    "shape-outside").valueSpan;
+  shapeOutsideToggle = shapeOutsideContainer.querySelector(".ruleview-shape");
+  ok(!clipPathShapeToggle.classList.contains("active"),
+     "clip-path toggle button is not active.");
+  ok(shapeOutsideToggle.classList.contains("active"),
+     "shape-outside toggle button is active.");
+});
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -18,18 +18,21 @@ var {getInplaceEditorForSpan: inplaceEdi
   require("devtools/client/shared/inplace-editor");
 
 const ROOT_TEST_DIR = getRootDirectory(gTestPath);
 const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
 
 const STYLE_INSPECTOR_L10N
       = new LocalizationHelper("devtools/shared/locales/styleinspector.properties");
 
+Services.prefs.setBoolPref("devtools.inspector.shapesHighlighter.enabled", true);
+
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.defaultColorUnit");
+  Services.prefs.clearUserPref("devtools.inspector.shapesHighlighter.enabled");
 });
 
 /**
  * The rule-view tests rely on a frame-script to be injected in the content test
  * page. So override the shared-head's addTab to load the frame script after the
  * tab was added.
  * FIXME: Refactor the rule-view tests to use the testActor instead of a frame
  * script, so they can run on remote targets too.
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -22,16 +22,17 @@ const Services = require("Services");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const SHARED_SWATCH_CLASS = "ruleview-swatch";
 const COLOR_SWATCH_CLASS = "ruleview-colorswatch";
 const BEZIER_SWATCH_CLASS = "ruleview-bezierswatch";
 const FILTER_SWATCH_CLASS = "ruleview-filterswatch";
 const ANGLE_SWATCH_CLASS = "ruleview-angleswatch";
+const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
 
 /*
  * An actionable element is an element which on click triggers a specific action
  * (e.g. shows a color tooltip, opens a link, …).
  */
 const ACTIONABLE_ELEMENTS_SELECTORS = [
   `.${COLOR_SWATCH_CLASS}`,
   `.${BEZIER_SWATCH_CLASS}`,
@@ -73,16 +74,17 @@ function TextPropertyEditor(ruleEditor, 
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
   this._onSwatchCommit = this._onSwatchCommit.bind(this);
   this._onSwatchPreview = this._onSwatchPreview.bind(this);
   this._onSwatchRevert = this._onSwatchRevert.bind(this);
   this._onValidate = this.ruleView.debounce(this._previewValue, 10, this);
   this.update = this.update.bind(this);
   this.updatePropertyState = this.updatePropertyState.bind(this);
+  this._onHoverShapePoint = this._onHoverShapePoint.bind(this);
 
   this._create();
   this.update();
 }
 
 TextPropertyEditor.prototype = {
   /**
    * Boolean indicating if the name or value is being currently edited.
@@ -295,16 +297,18 @@ TextPropertyEditor.prototype = {
         contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
         property: this.prop,
         popup: this.popup,
         multiline: true,
         maxWidth: () => this.container.getBoundingClientRect().width,
         cssProperties: this.cssProperties,
         contextMenu: this.ruleView.inspector.onTextBoxContextMenu
       });
+
+      this.ruleView.highlighters.on("hover-shape-point", this._onHoverShapePoint);
     }
   },
 
   /**
    * Get the path from which to resolve requests for this
    * rule's stylesheet.
    *
    * @return {String} the stylesheet's href.
@@ -353,16 +357,17 @@ TextPropertyEditor.prototype = {
       angleSwatchClass: SHARED_SWATCH_CLASS + " " + ANGLE_SWATCH_CLASS,
       bezierClass: "ruleview-bezier",
       bezierSwatchClass: SHARED_SWATCH_CLASS + " " + BEZIER_SWATCH_CLASS,
       colorClass: "ruleview-color",
       colorSwatchClass: SHARED_SWATCH_CLASS + " " + COLOR_SWATCH_CLASS,
       filterClass: "ruleview-filter",
       filterSwatchClass: SHARED_SWATCH_CLASS + " " + FILTER_SWATCH_CLASS,
       gridClass: "ruleview-grid",
+      shapeClass: "ruleview-shape",
       defaultColorType: !propDirty,
       urlClass: "theme-link",
       baseURI: this.sheetHref
     };
     let frag = outputParser.parseCssProperty(name, val, parserOptions);
     this.valueSpan.innerHTML = "";
     this.valueSpan.appendChild(frag);
 
@@ -437,16 +442,31 @@ TextPropertyEditor.prototype = {
     if (gridToggle) {
       gridToggle.setAttribute("title", l10n("rule.gridToggle.tooltip"));
       if (this.ruleView.highlighters.gridHighlighterShown ===
           this.ruleView.inspector.selection.nodeFront) {
         gridToggle.classList.add("active");
       }
     }
 
+    let shapeToggle = this.valueSpan.querySelector(".ruleview-shape");
+    if (shapeToggle) {
+      let mode = "css" + name.split("-").map(s => {
+        return s[0].toUpperCase() + s.slice(1);
+      }).join("");
+      shapeToggle.setAttribute("data-mode", mode);
+
+      let { highlighters, inspector } = this.ruleView;
+      if (highlighters.shapesHighlighterShown === inspector.selection.nodeFront &&
+          highlighters.state.shapes.options.mode === mode) {
+        shapeToggle.classList.add("active");
+        highlighters.highlightRuleViewShapePoint(highlighters.state.shapes.hoverPoint);
+      }
+    }
+
     // Now that we have updated the property's value, we might have a pending
     // click on the value container. If we do, we have to trigger a click event
     // on the right element.
     if (this._hasPendingClick) {
       this._hasPendingClick = false;
       let elToClick;
 
       if (this._clickedElementOptions !== null) {
@@ -924,12 +944,73 @@ TextPropertyEditor.prototype = {
    * Returns true if the property is a `display: [inline-]grid` declaration.
    *
    * @return {Boolean} true if the property is a `display: [inline-]grid` declaration.
    */
   isDisplayGrid: function () {
     return this.prop.name === "display" &&
       (this.prop.value === "grid" ||
        this.prop.value === "inline-grid");
-  }
+  },
+
+  /**
+   * Highlight the given shape point in the rule view. Called when "hover-shape-point"
+   * event is emitted.
+   *
+   * @param {Event} event
+   *        The "hover-shape-point" event.
+   * @param {String} point
+   *        The point to highlight.
+   */
+  _onHoverShapePoint: function (event, point) {
+    // If there is no shape toggle, or it is not active, return.
+    let shapeToggle = this.valueSpan.querySelector(".ruleview-shape.active");
+    if (!shapeToggle) {
+      return;
+    }
+
+    let view = this.ruleView;
+    let { highlighters } = view;
+    let ruleViewEl = view.element;
+    let selector = `.ruleview-shape-point.active`;
+    for (let pointNode of ruleViewEl.querySelectorAll(selector)) {
+      this._toggleShapePointActive(pointNode, false);
+    }
+
+    if (typeof point === "string") {
+      if (point.includes(",")) {
+        point = point.split(",")[0];
+      }
+      // Because one inset value can represent multiple points, inset points use classes
+      // instead of data.
+      selector = (INSET_POINT_TYPES.includes(point)) ?
+                 `.ruleview-shape-point.${point}` :
+                 `.ruleview-shape-point[data-point='${point}']`;
+      for (let pointNode of this.valueSpan.querySelectorAll(selector)) {
+        let nodeInfo = view.getNodeInfo(pointNode);
+        if (highlighters.isRuleViewShapePoint(nodeInfo)) {
+          this._toggleShapePointActive(pointNode, true);
+        }
+      }
+    }
+  },
+
+  /**
+   * Toggle the class "active" on the given shape point in the rule view if the current
+   * inspector selection is highlighted by the shapes highlighter.
+   *
+   * @param {NodeFront} node
+   *        The NodeFront of the shape point to toggle
+   * @param {Boolean} active
+   *        Whether the shape point should be active
+   */
+  _toggleShapePointActive: function (node, active) {
+    let { highlighters } = this.ruleView;
+    if (highlighters.inspector.selection.nodeFront !=
+        highlighters.shapesHighlighterShown) {
+      return;
+    }
+
+    node.classList.toggle("active", active);
+  },
 };
 
 exports.TextPropertyEditor = TextPropertyEditor;
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -4,19 +4,23 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
-const { VIEW_NODE_VALUE_TYPE } = require("devtools/client/inspector/shared/node-types");
+const {
+  VIEW_NODE_VALUE_TYPE,
+  VIEW_NODE_SHAPE_POINT_TYPE
+} = require("devtools/client/inspector/shared/node-types");
 
 const DEFAULT_GRID_COLOR = "#4B0082";
+const INSET_POINT_TYPES = ["top", "right", "bottom", "left"];
 
 /**
  * Highlighters overlay is a singleton managing all highlighters in the Inspector.
  *
  * @param  {Inspector} inspector
  *         Inspector toolbox panel.
  */
 function HighlightersOverlay(inspector) {
@@ -30,29 +34,34 @@ function HighlightersOverlay(inspector) 
   // NodeFront of element that is highlighted by the geometry editor.
   this.geometryEditorHighlighterShown = null;
   // NodeFront of the grid container that is highlighted.
   this.gridHighlighterShown = null;
   // Name of the highlighter shown on mouse hover.
   this.hoveredHighlighterShown = null;
   // Name of the selector highlighter shown.
   this.selectorHighlighterShown = null;
+  // NodeFront of the shape that is highlighted
+  this.shapesHighlighterShown = null;
   // Saved state to be restore on page navigation.
   this.state = {
-    // Only the grid highlighter state is saved at the moment.
-    grid: {}
+    grid: {},
+    shapes: {}
   };
 
   this.onClick = this.onClick.bind(this);
   this.onMarkupMutation = this.onMarkupMutation.bind(this);
   this.onMouseMove = this.onMouseMove.bind(this);
   this.onMouseOut = this.onMouseOut.bind(this);
   this.onWillNavigate = this.onWillNavigate.bind(this);
   this.onNavigate = this.onNavigate.bind(this);
+  this.showGridHighlighter = this.showGridHighlighter.bind(this);
+  this.showShapesHighlighter = this.showShapesHighlighter.bind(this);
   this._handleRejection = this._handleRejection.bind(this);
+  this._onHighlighterEvent = this._onHighlighterEvent.bind(this);
 
   // Add inspector events, not specific to a given view.
   this.inspector.on("markupmutation", this.onMarkupMutation);
   this.inspector.target.on("navigate", this.onNavigate);
   this.inspector.target.on("will-navigate", this.onWillNavigate);
 
   EventEmitter.decorate(this);
 }
@@ -97,16 +106,136 @@ HighlightersOverlay.prototype = {
 
     let el = view.element;
     el.removeEventListener("click", this.onClick, true);
     el.removeEventListener("mousemove", this.onMouseMove);
     el.removeEventListener("mouseout", this.onMouseOut);
   },
 
   /**
+   * Toggle the shapes highlighter for the given element with a shape.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the element with a shape to highlight.
+   * @param  {Object} options
+   *         Object used for passing options to the shapes highlighter.
+   */
+  toggleShapesHighlighter: Task.async(function* (node, options = {}) {
+    if (node == this.shapesHighlighterShown &&
+        options.mode === this.state.shapes.options.mode) {
+      yield this.hideShapesHighlighter(node);
+      return;
+    }
+
+    yield this.showShapesHighlighter(node, options);
+  }),
+
+  /**
+   * Show the shapes highlighter for the given element with a shape.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the element with a shape to highlight.
+   * @param  {Object} options
+   *         Object used for passing options to the shapes highlighter.
+   */
+  showShapesHighlighter: Task.async(function* (node, options) {
+    let highlighter = yield this._getHighlighter("ShapesHighlighter");
+    if (!highlighter) {
+      return;
+    }
+
+    let isShown = yield highlighter.show(node, options);
+    if (!isShown) {
+      return;
+    }
+
+    this.shapesHighlighterShown = node;
+    let { mode } = options;
+    this._toggleRuleViewIcon(node, false, ".ruleview-shape");
+    this._toggleRuleViewIcon(node, true, `.ruleview-shape[data-mode='${mode}']`);
+
+    try {
+      // Save shapes highlighter state.
+      let { url } = this.inspector.target;
+      let selector = yield node.getUniqueSelector();
+      this.state.shapes = { selector, options, url };
+
+      this.shapesHighlighterShown = node;
+      this.emit("shapes-highlighter-shown", node, options);
+    } catch (e) {
+      this._handleRejection(e);
+    }
+  }),
+
+  /**
+   * Hide the shapes highlighter for the given element with a shape.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the element with a shape to unhighlight.
+   */
+  hideShapesHighlighter: Task.async(function* (node) {
+    if (!this.shapesHighlighterShown || !this.highlighters.ShapesHighlighter) {
+      return;
+    }
+
+    this._toggleRuleViewIcon(node, false, ".ruleview-shape");
+
+    yield this.highlighters.ShapesHighlighter.hide();
+    this.emit("shapes-highlighter-hidden", this.shapesHighlighterShown,
+      this.state.shapes.options);
+    this.shapesHighlighterShown = null;
+    this.state.shapes = {};
+  }),
+
+  /**
+   * Show the shapes highlighter for the given element, with the given point highlighted.
+   *
+   * @param {NodeFront} node
+   *        The NodeFront of the element to highlight.
+   * @param {String} point
+   *        The point to highlight in the shapes highlighter.
+   */
+  hoverPointShapesHighlighter: Task.async(function* (node, point) {
+    if (node == this.shapesHighlighterShown) {
+      let options = Object.assign({}, this.state.shapes.options);
+      options.hoverPoint = point;
+      yield this.showShapesHighlighter(node, options);
+    }
+  }),
+
+  /**
+   * Highlight the given shape point in the rule view.
+   *
+   * @param {String} point
+   *        The point to highlight.
+   */
+  highlightRuleViewShapePoint: function (point) {
+    let view = this.inspector.getPanel("ruleview").view;
+    let ruleViewEl = view.element;
+    let selector = `.ruleview-shape-point.active`;
+    for (let pointNode of ruleViewEl.querySelectorAll(selector)) {
+      this._toggleShapePointActive(pointNode, false);
+    }
+
+    if (point !== null && point !== undefined) {
+      // Because one inset value can represent multiple points, inset points use classes
+      // instead of data.
+      selector = (INSET_POINT_TYPES.includes(point)) ?
+                 `.ruleview-shape-point.${point}` :
+                 `.ruleview-shape-point[data-point='${point}']`;
+      for (let pointNode of ruleViewEl.querySelectorAll(selector)) {
+        let nodeInfo = view.getNodeInfo(pointNode);
+        if (this.isRuleViewShapePoint(nodeInfo)) {
+          this._toggleShapePointActive(pointNode, true);
+        }
+      }
+    }
+  },
+
+  /**
    * Toggle the grid highlighter for the given grid container element.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element to highlight.
    * @param  {Object} options
    *         Object used for passing options to the grid highlighter.
    * @param. {String|null} trigger
    *         String name matching "grid" or "rule" to indicate where the
@@ -136,17 +265,17 @@ HighlightersOverlay.prototype = {
       return;
     }
 
     let isShown = yield highlighter.show(node, options);
     if (!isShown) {
       return;
     }
 
-    this._toggleRuleViewGridIcon(node, true);
+    this._toggleRuleViewIcon(node, true, ".ruleview-grid");
 
     if (trigger == "grid") {
       Services.telemetry.scalarAdd("devtools.grid.gridinspector.opened", 1);
     } else if (trigger == "rule") {
       Services.telemetry.scalarAdd("devtools.rules.gridinspector.opened", 1);
     }
 
     try {
@@ -170,17 +299,17 @@ HighlightersOverlay.prototype = {
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element to unhighlight.
    */
   hideGridHighlighter: Task.async(function* (node) {
     if (!this.gridHighlighterShown || !this.highlighters.CssGridHighlighter) {
       return;
     }
 
-    this._toggleRuleViewGridIcon(node, false);
+    this._toggleRuleViewIcon(node, false, ".ruleview-grid");
 
     yield this.highlighters.CssGridHighlighter.hide();
 
     // Emit the NodeFront of the grid container element that the grid highlighter was
     // hidden for.
     this.emit("grid-highlighter-hidden", this.gridHighlighterShown,
       this.state.grid.options);
     this.gridHighlighterShown = null;
@@ -236,43 +365,67 @@ HighlightersOverlay.prototype = {
 
     yield this.highlighters.GeometryEditorHighlighter.hide();
 
     this.emit("geometry-editor-highlighter-hidden");
     this.geometryEditorHighlighterShown = null;
   }),
 
   /**
+   * Handle events emitted by the highlighter.
+   *
+   * @param {Object} data
+   *        The data object sent in the event.
+   */
+  _onHighlighterEvent: function (data) {
+    if (data.type === "shape-hover-on") {
+      this.state.shapes.hoverPoint = data.point;
+      this.emit("hover-shape-point", data.point);
+    } else if (data.type === "shape-hover-off") {
+      this.state.shapes.hoverPoint = null;
+      this.emit("hover-shape-point", null);
+    }
+    this.emit("highlighter-event-handled");
+  },
+
+  /**
    * Restore the saved highlighter states.
-   *
+   * @param {String} name
+   *        The name of the highlighter to be restored
+   * @param {Object} state
+   *        The state of the highlighter to be restored
+   * @param {Function} showFunction
+   *        The function that shows the highlighter
    * @return {Promise} that resolves when the highlighter state was restored, and the
-   *          expected highlighters are displayed.
+   *         expected highlighters are displayed.
    */
-  restoreState: Task.async(function* () {
-    let { selector, options, url } = this.state.grid;
-
+  restoreState: Task.async(function* (name, state, showFunction) {
+    let { selector, options, url } = state;
     if (!selector || url !== this.inspector.target.url) {
       // Bail out if no selector was saved, or if we are on a different page.
-      this.emit("state-restored", { restored: false });
+      this.emit(`${name}-state-restored`, { restored: false });
       return;
     }
 
     // Wait for the new root to be ready in the inspector.
     yield this.onInspectorNewRoot;
 
     let walker = this.inspector.walker;
     let rootNode = yield walker.getRootNode();
     let nodeFront = yield walker.querySelector(rootNode, selector);
 
     if (nodeFront) {
-      yield this.showGridHighlighter(nodeFront, options);
-      this.emit("state-restored", { restored: true });
+      if (options.hoverPoint) {
+        options.hoverPoint = null;
+      }
+      yield showFunction(nodeFront, options);
+      this.emit(`${name}-state-restored`, { restored: true });
     }
 
-    this.emit("state-restored", { restored: false });
+    this.emit(`${name}-state-restored`, { restored: false });
   }),
 
   /**
    * Get a highlighter front given a type. It will only be initialized once.
    *
    * @param  {String} type
    *         The highlighter type. One of this.highlighters.
    * @return {Promise} that resolves to the highlighter
@@ -291,48 +444,68 @@ HighlightersOverlay.prototype = {
     } catch (e) {
       // Ignore any error
     }
 
     if (!highlighter) {
       return null;
     }
 
+    highlighter.on("highlighter-event", this._onHighlighterEvent);
     this.highlighters[type] = highlighter;
     return highlighter;
   }),
 
   _handleRejection: function (error) {
     if (!this.destroyed) {
       console.error(error);
     }
   },
 
   /**
-   * Toggle all the grid icons in the rule view if the current inspector selection is the
-   * highlighted node.
+   * Toggle all the icons with the given selector in the rule view if the current
+   * inspector selection is the highlighted node.
    *
    * @param  {NodeFront} node
-   *         The NodeFront of the grid container element to highlight.
+   *         The NodeFront of the element with a shape to highlight.
    * @param  {Boolean} active
-   *         Whether or not the grid icon should be active.
+   *         Whether or not the shape icon should be active.
+   * @param  {String} selector
+   *         The selector of the rule view icon to toggle.
    */
-  _toggleRuleViewGridIcon: function (node, active) {
+  _toggleRuleViewIcon: function (node, active, selector) {
     if (this.inspector.selection.nodeFront != node) {
       return;
     }
 
     let ruleViewEl = this.inspector.getPanel("ruleview").view.element;
 
-    for (let gridIcon of ruleViewEl.querySelectorAll(".ruleview-grid")) {
-      gridIcon.classList.toggle("active", active);
+    for (let icon of ruleViewEl.querySelectorAll(selector)) {
+      icon.classList.toggle("active", active);
     }
   },
 
   /**
+   * Toggle the class "active" on the given shape point in the rule view if the current
+   * inspector selection is highlighted by the shapes highlighter.
+   *
+   * @param {NodeFront} node
+   *        The NodeFront of the shape point to toggle
+   * @param {Boolean} active
+   *        Whether the shape point should be active
+   */
+  _toggleShapePointActive: function (node, active) {
+    if (this.inspector.selection.nodeFront != this.shapesHighlighterShown) {
+      return;
+    }
+
+    node.classList.toggle("active", active);
+  },
+
+  /**
    * Hide the currently shown hovered highlighter.
    */
   _hideHoveredHighlighter: function () {
     if (!this.hoveredHighlighterShown ||
         !this.highlighters[this.hoveredHighlighterShown]) {
       return;
     }
 
@@ -369,46 +542,75 @@ HighlightersOverlay.prototype = {
    * @param  {DOMNode} node
    * @return {Boolean}
    */
   _isRuleViewDisplayGrid: function (node) {
     return this.isRuleView && node.classList.contains("ruleview-grid");
   },
 
   /**
+   * Does the current clicked node have the shapes highlighter toggle in the
+   * rule-view.
+   *
+   * @param  {DOMNode} node
+   * @return {Boolean}
+   */
+  _isRuleViewShape: function (node) {
+    return this.isRuleView && node.classList.contains("ruleview-shape");
+  },
+
+  /**
    * Is the current hovered node a css transform property value in the rule-view.
    *
    * @param  {Object} nodeInfo
    * @return {Boolean}
    */
   _isRuleViewTransform: function (nodeInfo) {
     let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
                       nodeInfo.value.property === "transform";
     let isEnabled = nodeInfo.value.enabled &&
                     !nodeInfo.value.overridden &&
                     !nodeInfo.value.pseudoElement;
     return this.isRuleView && isTransform && isEnabled;
   },
 
-  onClick: function (event) {
-    // Bail out if the target is not a grid property value.
-    if (!this._isRuleViewDisplayGrid(event.target)) {
-      return;
-    }
-
-    event.stopPropagation();
+  /**
+   * Is the current hovered node a highlightable shape point in the rule-view.
+   *
+   * @param  {Object} nodeInfo
+   * @return {Boolean}
+   */
+  isRuleViewShapePoint: function (nodeInfo) {
+    let isShape = nodeInfo.type === VIEW_NODE_SHAPE_POINT_TYPE &&
+                  (nodeInfo.value.property === "clip-path" ||
+                  nodeInfo.value.property === "shape-outside");
+    let isEnabled = nodeInfo.value.enabled &&
+                    !nodeInfo.value.overridden &&
+                    !nodeInfo.value.pseudoElement;
+    return this.isRuleView && isShape && isEnabled && nodeInfo.value.toggleActive;
+  },
 
-    let { store } = this.inspector;
-    let { grids, highlighterSettings } = store.getState();
-    let grid = grids.find(g => g.nodeFront == this.inspector.selection.nodeFront);
+  onClick: function (event) {
+    if (this._isRuleViewDisplayGrid(event.target)) {
+      event.stopPropagation();
+
+      let { store } = this.inspector;
+      let { grids, highlighterSettings } = store.getState();
+      let grid = grids.find(g => g.nodeFront == this.inspector.selection.nodeFront);
 
-    highlighterSettings.color = grid ? grid.color : DEFAULT_GRID_COLOR;
+      highlighterSettings.color = grid ? grid.color : DEFAULT_GRID_COLOR;
 
-    this.toggleGridHighlighter(this.inspector.selection.nodeFront, highlighterSettings,
-      "rule");
+      this.toggleGridHighlighter(this.inspector.selection.nodeFront, highlighterSettings,
+        "rule");
+    } else if (this._isRuleViewShape(event.target)) {
+      event.stopPropagation();
+
+      let settings = { mode: event.target.dataset.mode };
+      this.toggleShapesHighlighter(this.inspector.selection.nodeFront, settings);
+    }
   },
 
   onMouseMove: function (event) {
     // Bail out if the target is the same as for the last mousemove.
     if (event.target === this._lastHovered) {
       return;
     }
 
@@ -420,16 +622,23 @@ HighlightersOverlay.prototype = {
     let view = this.isRuleView ?
       this.inspector.getPanel("ruleview").view :
       this.inspector.getPanel("computedview").computedView;
     let nodeInfo = view.getNodeInfo(event.target);
     if (!nodeInfo) {
       return;
     }
 
+    if (this.isRuleViewShapePoint(nodeInfo)) {
+      let { point } = nodeInfo.value;
+      this.hoverPointShapesHighlighter(this.inspector.selection.nodeFront, point);
+      this.emit("hover-shape-point", point);
+      return;
+    }
+
     // Choose the type of highlighter required for the hovered node.
     let type;
     if (this._isRuleViewTransform(nodeInfo) ||
         this._isComputedViewTransform(nodeInfo)) {
       type = "CssTransformHighlighter";
     }
 
     if (type) {
@@ -448,75 +657,103 @@ HighlightersOverlay.prototype = {
   onMouseOut: function (event) {
     // Only hide the highlighter if the mouse leaves the currently hovered node.
     if (!this._lastHovered ||
         (event && this._lastHovered.contains(event.relatedTarget))) {
       return;
     }
 
     // Otherwise, hide the highlighter.
+    let view = this.isRuleView ?
+      this.inspector.getPanel("ruleview").view :
+      this.inspector.getPanel("computedview").computedView;
+    let nodeInfo = view.getNodeInfo(this._lastHovered);
+    if (nodeInfo && this.isRuleViewShapePoint(nodeInfo)) {
+      this.hoverPointShapesHighlighter(this.inspector.selection.nodeFront, null);
+      this.emit("hover-shape-point", null);
+    }
     this._lastHovered = null;
     this._hideHoveredHighlighter();
   },
 
   /**
-   * Handler function for "markupmutation" events. Hides the grid highlighter if the grid
-   * container is no longer in the DOM tree.
+   * Handler function for "markupmutation" events. Hides the grid/shapes highlighter
+   * if the grid/shapes container is no longer in the DOM tree.
    */
   onMarkupMutation: Task.async(function* (evt, mutations) {
     let hasInterestingMutation = mutations.some(mut => mut.type === "childList");
-    if (!hasInterestingMutation || !this.gridHighlighterShown) {
+    if (!hasInterestingMutation) {
       // Bail out if the mutations did not remove nodes, or if no grid highlighter is
       // displayed.
       return;
     }
 
-    let nodeFront = this.gridHighlighterShown;
+    if (this.gridHighlighterShown) {
+      let nodeFront = this.gridHighlighterShown;
 
-    try {
-      let isInTree = yield this.inspector.walker.isInDOMTree(nodeFront);
-      if (!isInTree) {
-        this.hideGridHighlighter(nodeFront);
+      try {
+        let isInTree = yield this.inspector.walker.isInDOMTree(nodeFront);
+        if (!isInTree) {
+          this.hideGridHighlighter(nodeFront);
+        }
+      } catch (e) {
+        console.error(e);
       }
-    } catch (e) {
-      console.error(e);
+    }
+
+    if (this.shapesHighlighterShown) {
+      let nodeFront = this.shapesHighlighterShown;
+
+      try {
+        let isInTree = yield this.inspector.walker.isInDOMTree(nodeFront);
+        if (!isInTree) {
+          this.hideShapesHighlighter(nodeFront);
+        }
+      } catch (e) {
+        console.error(e);
+      }
     }
   }),
 
   /**
    * Restore saved highlighter state after navigate.
    */
   onNavigate: Task.async(function* () {
     try {
-      yield this.restoreState();
+      yield this.restoreState("grid", this.state.grid, this.showGridHighlighter);
+      yield this.restoreState("shapes", this.state.shapes, this.showShapesHighlighter);
     } catch (e) {
       this._handleRejection(e);
     }
   }),
 
   /**
    * Clear saved highlighter shown properties on will-navigate.
    */
   onWillNavigate: function () {
     this.geometryEditorHighlighterShown = null;
     this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
     this.selectorHighlighterShown = null;
+    this.shapesHighlighterShown = null;
 
     // The inspector panel should emit the new-root event when it is ready after navigate.
     this.onInspectorNewRoot = this.inspector.once("new-root");
   },
 
   /**
    * Destroy this overlay instance, removing it from the view and destroying
    * all initialized highlighters.
    */
   destroy: function () {
     for (let type in this.highlighters) {
       if (this.highlighters[type]) {
+        if (this.highlighters[type].off) {
+          this.highlighters[type].off("highlighter-event", this._onHighlighterEvent);
+        }
         this.highlighters[type].finalize();
         this.highlighters[type] = null;
       }
     }
 
     // Remove inspector events.
     this.inspector.off("markupmutation", this.onMarkupMutation);
     this.inspector.target.off("navigate", this.onNavigate);
@@ -529,14 +766,15 @@ HighlightersOverlay.prototype = {
     this.highlighterUtils = null;
     this.supportsHighlighters = null;
     this.state = null;
 
     this.geometryEditorHighlighterShown = null;
     this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
     this.selectorHighlighterShown = null;
+    this.shapesHighlighterShown = null;
 
     this.destroyed = true;
   }
 };
 
 module.exports = HighlightersOverlay;
--- a/devtools/client/inspector/shared/node-types.js
+++ b/devtools/client/inspector/shared/node-types.js
@@ -10,8 +10,9 @@
  * Types of nodes used in the rule and omputed view.
  */
 
 exports.VIEW_NODE_SELECTOR_TYPE = 1;
 exports.VIEW_NODE_PROPERTY_TYPE = 2;
 exports.VIEW_NODE_VALUE_TYPE = 3;
 exports.VIEW_NODE_IMAGE_URL_TYPE = 4;
 exports.VIEW_NODE_LOCATION_TYPE = 5;
+exports.VIEW_NODE_SHAPE_POINT_TYPE = 6;
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -74,16 +74,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_highlighter-cancel.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-cssgrid_01.js]
 [browser_inspector_highlighter-cssgrid_02.js]
 [browser_inspector_highlighter-cssshape_01.js]
 [browser_inspector_highlighter-cssshape_02.js]
 [browser_inspector_highlighter-cssshape_03.js]
 [browser_inspector_highlighter-cssshape_04.js]
+[browser_inspector_highlighter-cssshape_05.js]
 [browser_inspector_highlighter-csstransform_01.js]
 [browser_inspector_highlighter-csstransform_02.js]
 [browser_inspector_highlighter-embed.js]
 [browser_inspector_highlighter-eyedropper-clipboard.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_highlighter-eyedropper-csp.js]
 [browser_inspector_highlighter-eyedropper-events.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_05.js
@@ -0,0 +1,110 @@
+/* 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";
+
+// Test hovering over shape points in the rule-view and shapes highlighter.
+
+const TEST_URL = URL_ROOT + "doc_inspector_highlighter_cssshapes.html";
+
+const HIGHLIGHTER_TYPE = "ShapesHighlighter";
+const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
+
+add_task(function* () {
+  yield pushPref(CSS_SHAPES_ENABLED_PREF, true);
+  let env = yield openInspectorForURL(TEST_URL);
+  let helper = yield getHighlighterHelperFor(HIGHLIGHTER_TYPE)(env);
+  let { testActor, inspector } = env;
+  let view = selectRuleView(inspector);
+  let highlighters = view.highlighters;
+
+  yield highlightFromRuleView(inspector, view, highlighters, testActor);
+  yield highlightFromHighlighter(view, highlighters, testActor, helper);
+});
+
+function* highlightFromRuleView(inspector, view, highlighters, testActor) {
+  yield selectNode("#polygon", inspector);
+  yield toggleShapesHighlighter(view, highlighters, "#polygon", "clip-path", true);
+  let container = getRuleViewProperty(view, "#polygon", "clip-path").valueSpan;
+  let shapesToggle = container.querySelector(".ruleview-shape");
+
+  let highlighterFront = highlighters.highlighters[HIGHLIGHTER_TYPE];
+  let markerHidden = yield testActor.getHighlighterNodeAttribute(
+    "shapes-marker-hover", "hidden", highlighterFront);
+  ok(markerHidden, "Hover marker on highlighter is not visible");
+
+  info("Hover over point 0 in rule view");
+  let pointSpan = container.querySelector(".ruleview-shape-point[data-point='0']");
+  let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  EventUtils.synthesizeMouseAtCenter(pointSpan, {type: "mousemove"}, view.styleWindow);
+  yield onHighlighterShown;
+
+  ok(pointSpan.classList.contains("active"), "Hovered span is active");
+  is(highlighters.state.shapes.options.hoverPoint, "0",
+     "Hovered point is saved to state");
+
+  markerHidden = yield testActor.getHighlighterNodeAttribute(
+    "shapes-marker-hover", "hidden", highlighterFront);
+  ok(!markerHidden, "Marker on highlighter is visible");
+
+  info("Move mouse off point");
+  onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+  EventUtils.synthesizeMouseAtCenter(shapesToggle, {type: "mousemove"}, view.styleWindow);
+  yield onHighlighterShown;
+
+  ok(!pointSpan.classList.contains("active"), "Hovered span is no longer active");
+  is(highlighters.state.shapes.options.hoverPoint, null, "Hovered point is null");
+
+  markerHidden = yield testActor.getHighlighterNodeAttribute(
+    "shapes-marker-hover", "hidden", highlighterFront);
+  ok(markerHidden, "Marker on highlighter is not visible");
+
+  info("Hide shapes highlighter");
+  yield toggleShapesHighlighter(view, highlighters, "#polygon", "clip-path", false);
+}
+
+function* highlightFromHighlighter(view, highlighters, testActor, helper) {
+  let highlighterFront = highlighters.highlighters[HIGHLIGHTER_TYPE];
+  let { mouse } = helper;
+
+  yield toggleShapesHighlighter(view, highlighters, "#polygon", "clip-path", true);
+  let container = getRuleViewProperty(view, "#polygon", "clip-path").valueSpan;
+
+  info("Hover over first point in highlighter");
+  let onEventHandled = highlighters.once("highlighter-event-handled");
+  yield mouse.move(0, 0);
+  yield onEventHandled;
+  let markerHidden = yield testActor.getHighlighterNodeAttribute(
+    "shapes-marker-hover", "hidden", highlighterFront);
+  ok(!markerHidden, "Marker on highlighter is visible");
+
+  let pointSpan = container.querySelector(".ruleview-shape-point[data-point='0']");
+  ok(pointSpan.classList.contains("active"), "Span for point 0 is active");
+  is(highlighters.state.shapes.hoverPoint, "0", "Hovered point is saved to state");
+
+  info("Check that point is still highlighted after moving it");
+  yield mouse.down(0, 0);
+  yield mouse.move(10, 10);
+  yield mouse.up(10, 10);
+  markerHidden = yield testActor.getHighlighterNodeAttribute(
+    "shapes-marker-hover", "hidden", highlighterFront);
+  ok(!markerHidden, "Marker on highlighter is visible after moving point");
+
+  container = getRuleViewProperty(view, "element", "clip-path").valueSpan;
+  pointSpan = container.querySelector(".ruleview-shape-point[data-point='0']");
+  ok(pointSpan.classList.contains("active"),
+     "Span for point 0 is active after moving point");
+  is(highlighters.state.shapes.hoverPoint, "0",
+     "Hovered point is saved to state after moving point");
+
+  info("Move mouse off point");
+  onEventHandled = highlighters.once("highlighter-event-handled");
+  yield mouse.move(100, 100);
+  yield onEventHandled;
+  markerHidden = yield testActor.getHighlighterNodeAttribute(
+    "shapes-marker-hover", "hidden", highlighterFront);
+  ok(markerHidden, "Marker on highlighter is no longer visible");
+  ok(!pointSpan.classList.contains("active"), "Span for point 0 is no longer active");
+  is(highlighters.state.shapes.hoverPoint, null, "Hovered point is null");
+}
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -798,8 +798,38 @@ function* getDisplayedNodeTextContent(se
   yield inspector.markup.expandNode(container.node);
   yield waitForMultipleChildrenUpdates(inspector);
   if (container) {
     let textContainer = container.elt.querySelector("pre");
     return textContainer.textContent;
   }
   return null;
 }
+
+/**
+ * Toggle the shapes highlighter by simulating a click on the toggle
+ * in the rules view with the given selector and property
+ *
+ * @param {CssRuleView} view
+ *        The instance of the rule-view panel
+ * @param {Object} highlighters
+ *        The highlighters instance of the rule-view panel
+ * @param {String} selector
+ *        The selector in the rule-view to look for the property in
+ * @param {String} property
+ *        The name of the property
+ * @param {Boolean} show
+ *        If true, the shapes highlighter is being shown. If false, it is being hidden
+ */
+function* toggleShapesHighlighter(view, highlighters, selector, property, show) {
+  info("Toggle shapes highlighter");
+  let container = getRuleViewProperty(view, selector, property).valueSpan;
+  let shapesToggle = container.querySelector(".ruleview-shape");
+  if (show) {
+    let onHighlighterShown = highlighters.once("shapes-highlighter-shown");
+    shapesToggle.click();
+    yield onHighlighterShown;
+  } else {
+    let onHighlighterHidden = highlighters.once("shapes-highlighter-hidden");
+    shapesToggle.click();
+    yield onHighlighterHidden;
+  }
+}
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -59,16 +59,18 @@ pref("devtools.inspector.imagePreviewToo
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content (like controls in <video> tags)
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Enable the MDN docs tooltip
 pref("devtools.inspector.mdnDocsTooltip.enabled", false);
 // Enable the new color widget
 pref("devtools.inspector.colorWidget.enabled", false);
+// Enable the CSS shapes highlighter
+pref("devtools.inspector.shapesHighlighter.enabled", false);
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Counter to promote the inspector layout view.
 // @remove after release 56 (See Bug 1355747)
 pref("devtools.promote.layoutview", 1);
 // Whether or not to show the promote bar in the layout view
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -5,24 +5,27 @@
 "use strict";
 
 const {angleUtils} = require("devtools/client/shared/css-angle");
 const {colorUtils} = require("devtools/shared/css/color");
 const {getCSSLexer} = require("devtools/shared/css/lexer");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {
   ANGLE_TAKING_FUNCTIONS,
+  BASIC_SHAPE_FUNCTIONS,
   BEZIER_KEYWORDS,
   COLOR_TAKING_FUNCTIONS,
   CSS_TYPES
 } = require("devtools/shared/css/properties-db");
+const {appendText} = require("devtools/client/inspector/shared/utils");
 const Services = require("Services");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const CSS_GRID_ENABLED_PREF = "layout.css.grid.enabled";
+const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
 
 /**
  * This module is used to process text for output by developer tools. This means
  * linking JS files with the debugger, CSS files with the style editor, JS
  * functions with the debugger, placing color swatches next to colors and
  * adding doorhanger previews where possible (images, angles, lengths,
  * border radius, cubic-bezier etc.).
  *
@@ -72,16 +75,17 @@ OutputParser.prototype = {
    *         A document fragment containing color swatches etc.
    */
   parseCssProperty: function (name, value, options = {}) {
     options = this._mergeOptions(options);
 
     options.expectCubicBezier = this.supportsType(name, CSS_TYPES.TIMING_FUNCTION);
     options.expectDisplay = name === "display";
     options.expectFilter = name === "filter";
+    options.expectShape = name === "clip-path" || name === "shape-outside";
     options.supportsColor = this.supportsType(name, CSS_TYPES.COLOR) ||
                             this.supportsType(name, CSS_TYPES.GRADIENT);
 
     // The filter property is special in that we want to show the
     // swatch even if the value is invalid, because this way the user
     // can easily use the editor to fix it.
     if (options.expectFilter || this._cssPropertySupportsValue(name, value)) {
       return this._parse(value, options);
@@ -157,24 +161,22 @@ OutputParser.prototype = {
          outerMostFunctionTakesColor);
     };
 
     let angleOK = function (angle) {
       return (new angleUtils.CssAngle(angle)).valid;
     };
 
     let spaceNeeded = false;
-    while (true) {
-      let token = tokenStream.nextToken();
-      if (!token) {
-        break;
-      }
+    let token = tokenStream.nextToken();
+    while (token) {
       if (token.tokenType === "comment") {
         // This doesn't change spaceNeeded, because we didn't emit
         // anything to the output.
+        token = tokenStream.nextToken();
         continue;
       }
 
       switch (token.tokenType) {
         case "function": {
           if (COLOR_TAKING_FUNCTIONS.includes(token.text) ||
               ANGLE_TAKING_FUNCTIONS.includes(token.text)) {
             // The function can accept a color or an angle argument, and we know
@@ -192,16 +194,20 @@ OutputParser.prototype = {
             let functionText = this._collectFunctionText(token, text,
                                                          tokenStream);
 
             if (options.expectCubicBezier && token.text === "cubic-bezier") {
               this._appendCubicBezier(functionText, options);
             } else if (colorOK() &&
                        colorUtils.isValidCSSColor(functionText, this.cssColor4)) {
               this._appendColor(functionText, options);
+            } else if (options.expectShape &&
+                       Services.prefs.getBoolPref(CSS_SHAPES_ENABLED_PREF) &&
+                       BASIC_SHAPE_FUNCTIONS.includes(token.text)) {
+              this._appendShape(functionText, options);
             } else {
               this._appendTextNode(functionText);
             }
           }
           break;
         }
 
         case "ident":
@@ -268,16 +274,18 @@ OutputParser.prototype = {
       }
 
       // If this token might possibly introduce token pasting when
       // color-cycling, require a space.
       spaceNeeded = (token.tokenType === "ident" || token.tokenType === "at" ||
                      token.tokenType === "id" || token.tokenType === "hash" ||
                      token.tokenType === "number" || token.tokenType === "dimension" ||
                      token.tokenType === "percentage" || token.tokenType === "dimension");
+
+      token = tokenStream.nextToken();
     }
 
     let result = this._toDOM();
 
     if (options.expectFilter && !options.filterSwatch) {
       result = this._wrapFilter(text, options, result);
     }
 
@@ -349,16 +357,548 @@ OutputParser.prototype = {
     value.textContent = grid;
 
     container.appendChild(toggle);
     container.appendChild(value);
     this.parsed.push(container);
   },
 
   /**
+   * Append a CSS shapes highlighter toggle next to the value, and parse the value
+   * into spans, each containing a point that can be hovered over.
+   *
+   * @param {String} shape
+   *        The shape text value to append
+   * @param {Object} options
+   *        Options object. For valid options and default values see
+   *        _mergeOptions()
+   */
+  _appendShape: function (shape, options) {
+    const shapeTypes = [{
+      prefix: "polygon(",
+      coordParser: this._addPolygonPointNodes.bind(this)
+    }, {
+      prefix: "circle(",
+      coordParser: this._addCirclePointNodes.bind(this)
+    }, {
+      prefix: "ellipse(",
+      coordParser: this._addEllipsePointNodes.bind(this)
+    }, {
+      prefix: "inset(",
+      coordParser: this._addInsetPointNodes.bind(this)
+    }];
+
+    let container = this._createNode("span", {});
+
+    let toggle = this._createNode("span", {
+      class: options.shapeClass
+    });
+
+    for (let { prefix, coordParser } of shapeTypes) {
+      if (shape.includes(prefix)) {
+        let coordsBegin = prefix.length;
+        let coordsEnd = shape.lastIndexOf(")");
+        let valContainer = this._createNode("span", {});
+
+        container.appendChild(toggle);
+
+        appendText(valContainer, shape.substring(0, coordsBegin));
+
+        let coordsString = shape.substring(coordsBegin, coordsEnd);
+        valContainer = coordParser(coordsString, valContainer);
+
+        appendText(valContainer, shape.substring(coordsEnd));
+        container.appendChild(valContainer);
+      }
+    }
+
+    this.parsed.push(container);
+  },
+
+  /**
+   * Parse the given polygon coordinates and create a span for each coordinate pair,
+   * adding it to the given container node.
+   *
+   * @param {String} coords
+   *        The string of coordinate pairs.
+   * @param {Node} container
+   *        The node to which spans containing points are added.
+   * @returns {Node} The container to which spans have been added.
+   */
+  _addPolygonPointNodes: function (coords, container) {
+    let tokenStream = getCSSLexer(coords);
+    let token = tokenStream.nextToken();
+    let coord = "";
+    let i = 0;
+    let depth = 0;
+    let isXCoord = true;
+    let fillRule = false;
+    let coordNode = this._createNode("span", {
+      class: "ruleview-shape-point",
+      "data-point": `${i}`,
+    });
+
+    while (token) {
+      if (token.tokenType === "symbol" && token.text === ",") {
+        // Comma separating coordinate pairs; add coordNode to container and reset vars
+        if (!isXCoord) {
+          // Y coord not added to coordNode yet
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": `${i}`,
+            "data-pair": (isXCoord) ? "x" : "y"
+          }, coord);
+          coordNode.appendChild(node);
+          coord = "";
+          isXCoord = !isXCoord;
+        }
+
+        if (fillRule) {
+          // If the last text added was a fill-rule, do not increment i.
+          fillRule = false;
+        } else {
+          container.appendChild(coordNode);
+          i++;
+        }
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+        coord = "";
+        depth = 0;
+        isXCoord = true;
+        coordNode = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": `${i}`,
+        });
+      } else if (token.tokenType === "symbol" && token.text === "(") {
+        depth++;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "symbol" && token.text === ")") {
+        depth--;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "whitespace" && coord === "") {
+        // Whitespace at beginning of coord; add to container
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+      } else if (token.tokenType === "whitespace" && depth === 0) {
+        // Whitespace signifying end of coord
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": `${i}`,
+          "data-pair": (isXCoord) ? "x" : "y"
+        }, coord);
+        coordNode.appendChild(node);
+        appendText(coordNode, coords.substring(token.startOffset, token.endOffset));
+        coord = "";
+        isXCoord = !isXCoord;
+      } else if ((token.tokenType === "number" || token.tokenType === "dimension" ||
+                  token.tokenType === "percentage" || token.tokenType === "function")) {
+        if (isXCoord && coord && depth === 0) {
+          // Whitespace is not necessary between x/y coords.
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": `${i}`,
+            "data-pair": "x"
+          }, coord);
+          coordNode.appendChild(node);
+          isXCoord = false;
+          coord = "";
+        }
+
+        coord += coords.substring(token.startOffset, token.endOffset);
+        if (token.tokenType === "function") {
+          depth++;
+        }
+      } else if (token.tokenType === "ident" &&
+                 (token.text === "nonzero" || token.text === "evenodd")) {
+        // A fill-rule (nonzero or evenodd).
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+        fillRule = true;
+      } else {
+        coord += coords.substring(token.startOffset, token.endOffset);
+      }
+      token = tokenStream.nextToken();
+    }
+
+    // Add coords if any are left over
+    if (coord) {
+      let node = this._createNode("span", {
+        class: "ruleview-shape-point",
+        "data-point": `${i}`,
+        "data-pair": (isXCoord) ? "x" : "y"
+      }, coord);
+      coordNode.appendChild(node);
+      container.appendChild(coordNode);
+    }
+    return container;
+  },
+
+  /**
+   * Parse the given circle coordinates and populate the given container appropriately
+   * with a separate span for the center point.
+   *
+   * @param {String} coords
+   *        The circle definition.
+   * @param {Node} container
+   *        The node to which the definition is added.
+   * @returns {Node} The container to which the definition has been added.
+   */
+  _addCirclePointNodes: function (coords, container) {
+    let tokenStream = getCSSLexer(coords);
+    let token = tokenStream.nextToken();
+    let depth = 0;
+    let coord = "";
+    let point = "radius";
+    let centerNode = this._createNode("span", {
+      class: "ruleview-shape-point",
+      "data-point": "center"
+    });
+    while (token) {
+      if (token.tokenType === "symbol" && token.text === "(") {
+        depth++;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "symbol" && token.text === ")") {
+        depth--;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "whitespace" && coord === "") {
+        // Whitespace at beginning of coord; add to container
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+      } else if (token.tokenType === "whitespace" && point === "radius" && depth === 0) {
+        // Whitespace signifying end of radius
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": "radius"
+        }, coord);
+        container.appendChild(node);
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+        point = "cx";
+        coord = "";
+        depth = 0;
+      } else if (token.tokenType === "whitespace" && depth === 0) {
+        // Whitespace signifying end of cx/cy
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": "center",
+          "data-pair": (point === "cx") ? "x" : "y"
+        }, coord);
+        centerNode.appendChild(node);
+        appendText(centerNode, coords.substring(token.startOffset, token.endOffset));
+        point = (point === "cx") ? "cy" : "cx";
+        coord = "";
+        depth = 0;
+      } else if (token.tokenType === "ident" && token.text === "at") {
+        // "at"; Add radius to container if not already done so
+        if (point === "radius" && coord) {
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": "radius"
+          }, coord);
+          container.appendChild(node);
+        }
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+        point = "cx";
+        coord = "";
+        depth = 0;
+      } else if ((token.tokenType === "number" || token.tokenType === "dimension" ||
+                  token.tokenType === "percentage" || token.tokenType === "function")) {
+        if (point === "cx" && coord && depth === 0) {
+          // Center coords don't require whitespace between x/y. So if current point is
+          // cx, we have the cx coord, and depth is 0, then this token is actually cy.
+          // Add cx to centerNode and set point to cy.
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": "center",
+            "data-pair": "x"
+          }, coord);
+          centerNode.appendChild(node);
+          point = "cy";
+          coord = "";
+        }
+
+        coord += coords.substring(token.startOffset, token.endOffset);
+        if (token.tokenType === "function") {
+          depth++;
+        }
+      } else {
+        coord += coords.substring(token.startOffset, token.endOffset);
+      }
+      token = tokenStream.nextToken();
+    }
+
+    // Add coords if any are left over.
+    if (coord) {
+      if (point === "radius") {
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": "radius"
+        }, coord);
+        container.appendChild(node);
+      } else {
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": "center",
+          "data-pair": (point === "cx") ? "x" : "y"
+        }, coord);
+        centerNode.appendChild(node);
+      }
+    }
+
+    if (centerNode.textContent) {
+      container.appendChild(centerNode);
+    }
+    return container;
+  },
+
+  /**
+   * Parse the given ellipse coordinates and populate the given container appropriately
+   * with a separate span for each point
+   *
+   * @param {String} coords
+   *        The ellipse definition.
+   * @param {Node} container
+   *        The node to which the definition is added.
+   * @returns {Node} The container to which the definition has been added.
+   */
+  _addEllipsePointNodes: function (coords, container) {
+    let tokenStream = getCSSLexer(coords);
+    let token = tokenStream.nextToken();
+    let depth = 0;
+    let coord = "";
+    let point = "rx";
+    let centerNode = this._createNode("span", {
+      class: "ruleview-shape-point",
+      "data-point": "center"
+    });
+    while (token) {
+      if (token.tokenType === "symbol" && token.text === "(") {
+        depth++;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "symbol" && token.text === ")") {
+        depth--;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "whitespace" && coord === "") {
+        // Whitespace at beginning of coord; add to container
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+      } else if (token.tokenType === "whitespace" && depth === 0) {
+        if (point === "rx" || point === "ry") {
+          // Whitespace signifying end of rx/ry
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": point,
+          }, coord);
+          container.appendChild(node);
+          appendText(container, coords.substring(token.startOffset, token.endOffset));
+          point = (point === "rx") ? "ry" : "cx";
+          coord = "";
+          depth = 0;
+        } else {
+          // Whitespace signifying end of cx/cy
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": "center",
+            "data-pair": (point === "cx") ? "x" : "y"
+          }, coord);
+          centerNode.appendChild(node);
+          appendText(centerNode, coords.substring(token.startOffset, token.endOffset));
+          point = (point === "cx") ? "cy" : "cx";
+          coord = "";
+          depth = 0;
+        }
+      } else if (token.tokenType === "ident" && token.text === "at") {
+        // "at"; Add radius to container if not already done so
+        if (point === "ry" && coord) {
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": "ry"
+          }, coord);
+          container.appendChild(node);
+        }
+        appendText(container, coords.substring(token.startOffset, token.endOffset));
+        point = "cx";
+        coord = "";
+        depth = 0;
+      } else if ((token.tokenType === "number" || token.tokenType === "dimension" ||
+                  token.tokenType === "percentage" || token.tokenType === "function")) {
+        if (point === "rx" && coord && depth === 0) {
+          // Radius coords don't require whitespace between x/y.
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": "rx",
+          }, coord);
+          container.appendChild(node);
+          point = "ry";
+          coord = "";
+        }
+        if (point === "cx" && coord && depth === 0) {
+          // Center coords don't require whitespace between x/y.
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+            "data-point": "center",
+            "data-pair": "x"
+          }, coord);
+          centerNode.appendChild(node);
+          point = "cy";
+          coord = "";
+        }
+
+        coord += coords.substring(token.startOffset, token.endOffset);
+        if (token.tokenType === "function") {
+          depth++;
+        }
+      } else {
+        coord += coords.substring(token.startOffset, token.endOffset);
+      }
+      token = tokenStream.nextToken();
+    }
+
+    // Add coords if any are left over.
+    if (coord) {
+      if (point === "rx" || point === "ry") {
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": point
+        }, coord);
+        container.appendChild(node);
+      } else {
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+          "data-point": "center",
+          "data-pair": (point === "cx") ? "x" : "y"
+        }, coord);
+        centerNode.appendChild(node);
+      }
+    }
+
+    if (centerNode.textContent) {
+      container.appendChild(centerNode);
+    }
+    return container;
+  },
+
+  /**
+   * Parse the given inset coordinates and populate the given container appropriately.
+   *
+   * @param {String} coords
+   *        The inset definition.
+   * @param {Node} container
+   *        The node to which the definition is added.
+   * @returns {Node} The container to which the definition has been added.
+   */
+  _addInsetPointNodes: function (coords, container) {
+    const insetPoints = ["top", "right", "bottom", "left"];
+    let tokenStream = getCSSLexer(coords);
+    let token = tokenStream.nextToken();
+    let depth = 0;
+    let coord = "";
+    let i = 0;
+    let round = false;
+    // nodes is an array containing all the coordinate spans. otherText is an array of
+    // arrays, each containing the text that should be inserted into container before
+    // the node with the same index. i.e. all elements of otherText[i] is inserted
+    // into container before nodes[i].
+    let nodes = [];
+    let otherText = [[]];
+
+    while (token) {
+      if (round) {
+        // Everything that comes after "round" should just be plain text
+        otherText[i].push(coords.substring(token.startOffset, token.endOffset));
+      } else if (token.tokenType === "symbol" && token.text === "(") {
+        depth++;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "symbol" && token.text === ")") {
+        depth--;
+        coord += coords.substring(token.startOffset, token.endOffset);
+      } else if (token.tokenType === "whitespace" && coord === "") {
+        // Whitespace at beginning of coord; add to container
+        otherText[i].push(coords.substring(token.startOffset, token.endOffset));
+      } else if (token.tokenType === "whitespace" && depth === 0) {
+        // Whitespace signifying end of coord; create node and push to nodes
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point"
+        }, coord);
+        nodes.push(node);
+        i++;
+        coord = "";
+        otherText[i] = [coords.substring(token.startOffset, token.endOffset)];
+        depth = 0;
+      } else if ((token.tokenType === "number" || token.tokenType === "dimension" ||
+                  token.tokenType === "percentage" || token.tokenType === "function")) {
+        if (coord && depth === 0) {
+          // Inset coords don't require whitespace between each coord.
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+          }, coord);
+          nodes.push(node);
+          i++;
+          coord = "";
+          otherText[i] = [];
+        }
+
+        coord += coords.substring(token.startOffset, token.endOffset);
+        if (token.tokenType === "function") {
+          depth++;
+        }
+      } else if (token.tokenType === "ident" && token.text === "round") {
+        if (coord && depth === 0) {
+          // Whitespace is not necessary before "round"; create a new node for the coord
+          let node = this._createNode("span", {
+            class: "ruleview-shape-point",
+          }, coord);
+          nodes.push(node);
+          i++;
+          coord = "";
+          otherText[i] = [];
+        }
+        round = true;
+        otherText[i].push(coords.substring(token.startOffset, token.endOffset));
+      } else {
+        coord += coords.substring(token.startOffset, token.endOffset);
+      }
+      token = tokenStream.nextToken();
+    }
+
+    // Take care of any leftover text
+    if (coord) {
+      if (round) {
+        otherText[i].push(coord);
+      } else {
+        let node = this._createNode("span", {
+          class: "ruleview-shape-point",
+        }, coord);
+        nodes.push(node);
+      }
+    }
+
+    // insetPoints contains the 4 different possible inset points in the order they are
+    // defined. By taking the modulo of the index in insetPoints with the number of nodes,
+    // we can get which node represents each point (e.g. if there is only 1 node, it
+    // represents all 4 points). The exception is "left" when there are 3 nodes. In that
+    // case, it is nodes[1] that represents the left point rather than nodes[0].
+    for (let j = 0; j < 4; j++) {
+      let point = insetPoints[j];
+      let nodeIndex = (point === "left" && nodes.length === 3) ? 1 : j % nodes.length;
+      nodes[nodeIndex].classList.add(point);
+    }
+
+    nodes.forEach((node, j, array) => {
+      for (let text of otherText[j]) {
+        appendText(container, text);
+      }
+      container.appendChild(node);
+    });
+
+    // Add text that comes after the last node, if any exists
+    if (otherText[nodes.length]) {
+      for (let text of otherText[nodes.length]) {
+        appendText(container, text);
+      }
+    }
+
+    return container;
+  },
+
+  /**
    * Append a angle value to the output
    *
    * @param {String} angle
    *        angle to append
    * @param {Object} options
    *        Options object. For valid options and default values see
    *        _mergeOptions()
    */
@@ -692,16 +1232,17 @@ OutputParser.prototype = {
    *                                    // that follows the swatch.
    *           - colorSwatchClass: ""   // The class to use for color swatches.
    *           - filterSwatch: false    // A special case for parsing a
    *                                    // "filter" property, causing the
    *                                    // parser to skip the call to
    *                                    // _wrapFilter.  Used only for
    *                                    // previewing with the filter swatch.
    *           - gridClass: ""          // The class to use for the grid icon.
+   *           - shapeClass: ""         // The class to use for the shape icon.
    *           - supportsColor: false   // Does the CSS property support colors?
    *           - urlClass: ""           // The class to be used for url() links.
    *           - baseURI: undefined     // A string used to resolve
    *                                    // relative links.
    * @return {Object}
    *         Overridden options object
    */
   _mergeOptions: function (overrides) {
@@ -710,16 +1251,17 @@ OutputParser.prototype = {
       angleClass: "",
       angleSwatchClass: "",
       bezierClass: "",
       bezierSwatchClass: "",
       colorClass: "",
       colorSwatchClass: "",
       filterSwatch: false,
       gridClass: "",
+      shapeClass: "",
       supportsColor: false,
       urlClass: "",
       baseURI: undefined,
     };
 
     for (let item in overrides) {
       defaults[item] = overrides[item];
     }
--- a/devtools/client/shared/test/browser_outputparser.js
+++ b/devtools/client/shared/test/browser_outputparser.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const OutputParser = require("devtools/client/shared/output-parser");
 const {initCssProperties, getCssProperties} = require("devtools/shared/fronts/css-properties");
+const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
 
 add_task(function* () {
   yield addTab("about:blank");
   yield performTest();
   gBrowser.removeCurrentTab();
 });
 
 function* performTest() {
@@ -22,16 +23,17 @@ function* performTest() {
   let cssProperties = getCssProperties(toolbox);
 
   let parser = new OutputParser(doc, cssProperties);
   testParseCssProperty(doc, parser);
   testParseCssVar(doc, parser);
   testParseURL(doc, parser);
   testParseFilter(doc, parser);
   testParseAngle(doc, parser);
+  testParseShape(doc, parser);
 
   host.destroy();
 }
 
 // Class name used in color swatch.
 var COLOR_TEST_CLASS = "test-class";
 
 // Create a new CSS color-parsing test.  |name| is the name of the CSS
@@ -288,8 +290,126 @@ function testParseAngle(doc, parser) {
   frag = parser.parseCssProperty("background-image",
     "linear-gradient(90deg, red, blue", {
       angleSwatchClass: "test-angleswatch"
     });
 
   swatchCount = frag.querySelectorAll(".test-angleswatch").length;
   is(swatchCount, 1, "angle swatch was created");
 }
+
+function testParseShape(doc, parser) {
+  info("Test shape parsing");
+  pushPref(CSS_SHAPES_ENABLED_PREF, true);
+  const tests = [
+    {
+      desc: "Polygon shape",
+      definition: "polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n "
+                  + "12em var(--variable), 100% 100%) margin-box",
+      spanCount: 18
+    },
+    {
+      desc: "Invalid polygon shape",
+      definition: "polygon(0px 0px 100px 20px, 20% 20%)",
+      spanCount: 0
+    },
+    {
+      desc: "Circle shape with all arguments",
+      definition: "circle(25% at\n 30% 200px) border-box",
+      spanCount: 4
+    },
+    {
+      desc: "Circle shape with only one center",
+      definition: "circle(25em at 40%)",
+      spanCount: 3
+    },
+    {
+      desc: "Circle shape with no radius",
+      definition: "circle(at 30% 40%)",
+      spanCount: 3
+    },
+    {
+      desc: "Circle shape with no center",
+      definition: "circle(12em)",
+      spanCount: 1
+    },
+    {
+      desc: "Circle shape with no arguments",
+      definition: "circle()",
+      spanCount: 0
+    },
+    {
+      desc: "Circle shape with no space before at",
+      definition: "circle(25%at 30% 30%)",
+      spanCount: 4
+    },
+    {
+      desc: "Invalid circle shape",
+      definition: "circle(25%at30%30%)",
+      spanCount: 0
+    },
+    {
+      desc: "Ellipse shape with all arguments",
+      definition: "ellipse(200px 10em at 25% 120px) content-box",
+      spanCount: 5
+    },
+    {
+      desc: "Ellipse shape with only one center",
+      definition: "ellipse(200px 10% at 120px)",
+      spanCount: 4
+    },
+    {
+      desc: "Ellipse shape with no radius",
+      definition: "ellipse(at 25% 120px)",
+      spanCount: 3
+    },
+    {
+      desc: "Ellipse shape with no center",
+      definition: "ellipse(200px\n10em)",
+      spanCount: 2
+    },
+    {
+      desc: "Ellipse shape with no arguments",
+      definition: "ellipse()",
+      spanCount: 0
+    },
+    {
+      desc: "Invalid ellipse shape",
+      definition: "ellipse(200px100px at 30$ 20%)",
+      spanCount: 0
+    },
+    {
+      desc: "Inset shape with 4 arguments",
+      definition: "inset(200px 100px\n 30%15%)",
+      spanCount: 4
+    },
+    {
+      desc: "Inset shape with 3 arguments",
+      definition: "inset(200px 100px 15%)",
+      spanCount: 3
+    },
+    {
+      desc: "Inset shape with 2 arguments",
+      definition: "inset(200px 100px)",
+      spanCount: 2
+    },
+    {
+      desc: "Inset shape with 1 argument",
+      definition: "inset(200px)",
+      spanCount: 1
+    },
+    {
+      desc: "Inset shape with 0 arguments",
+      definition: "inset()",
+      spanCount: 0
+    }
+  ];
+
+  for (let {desc, definition, spanCount} of tests) {
+    info(desc);
+    let frag = parser.parseCssProperty("clip-path", definition, {
+      shapeClass: "ruleview-shape"
+    });
+    let spans = frag.querySelectorAll(".ruleview-shape-point");
+    is(spans.length, spanCount, desc + " span count");
+    is(frag.textContent, definition, desc + " text content");
+  }
+}
--- a/devtools/client/sourceeditor/codemirror/README
+++ b/devtools/client/sourceeditor/codemirror/README
@@ -1,16 +1,16 @@
 This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
 is a JavaScript component that provides a code editor in the browser. When
 a mode is available for the language you are coding in, it will color your
 code, and optionally help with indentation.
 
 # Upgrade
 
-Currently used version is 5.27.4. To upgrade: download a new version of
+Currently used version is 5.28.0. To upgrade: download a new version of
 CodeMirror from the project's page [1] and replace all JavaScript and
 CSS files inside the codemirror directory [2].
 
 Then to recreate codemirror.bundle.js:
  > cd devtools/client/sourceeditor
  > npm install
  > webpack
 
--- a/devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
+++ b/devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
@@ -60,16 +60,18 @@
 
   function makeWidget(cm, options) {
     var widget = getOption(cm, options, "widget");
     if (typeof widget == "string") {
       var text = document.createTextNode(widget);
       widget = document.createElement("span");
       widget.appendChild(text);
       widget.className = "CodeMirror-foldmarker";
+    } else if (widget) {
+      widget = widget.cloneNode(true)
     }
     return widget;
   }
 
   // Clumsy backwards-compatible interface
   CodeMirror.newFoldFunction = function(rangeFinder, widget) {
     return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
   };
--- a/devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
+++ b/devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
@@ -123,21 +123,23 @@
     doFold = function(str) { return str.toLowerCase() }
     noFold = function(str) { return str }
   }
 
   // Maps a position in a case-folded line back to a position in the original line
   // (compensating for codepoints increasing in number during folding)
   function adjustPos(orig, folded, pos, foldFunc) {
     if (orig.length == folded.length) return pos
-    for (var pos1 = Math.min(pos, orig.length);;) {
-      var len1 = foldFunc(orig.slice(0, pos1)).length
-      if (len1 < pos) ++pos1
-      else if (len1 > pos) --pos1
-      else return pos1
+    for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
+      if (min == max) return min
+      var mid = (min + max) >> 1
+      var len = foldFunc(orig.slice(0, mid)).length
+      if (len == pos) return mid
+      else if (len > pos) max = mid
+      else min = mid + 1
     }
   }
 
   function searchStringForward(doc, query, start, caseFold) {
     // Empty string would match anything and never progress, so we
     // define it to match nothing instead.
     if (!query.length) return null
     var fold = caseFold ? doFold : noFold
--- a/devtools/client/sourceeditor/codemirror/addon/tern/tern.js
+++ b/devtools/client/sourceeditor/codemirror/addon/tern/tern.js
@@ -329,17 +329,21 @@
       if (arg.type != "?") {
         tip.appendChild(document.createTextNode(":\u00a0"));
         tip.appendChild(elt("span", cls + "type", arg.type));
       }
     }
     tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
     if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
     var place = cm.cursorCoords(null, "page");
-    ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
+    var tooltip = ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip)
+    setTimeout(function() {
+      tooltip.clear = onEditorActivity(cm, function() {
+        if (ts.activeArgHints == tooltip) closeArgHints(ts) })
+    }, 20)
   }
 
   function parseFnType(text) {
     var args = [], pos = 3;
 
     function skipMatching(upto) {
       var depth = 0, start = pos;
       for (;;) {
@@ -599,34 +603,42 @@
     var where = cm.cursorCoords();
     var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
     function maybeClear() {
       old = true;
       if (!mouseOnTip) clear();
     }
     function clear() {
       cm.state.ternTooltip = null;
-      if (!tip.parentNode) return;
-      cm.off("cursorActivity", clear);
-      cm.off('blur', clear);
-      cm.off('scroll', clear);
-      fadeOut(tip);
+      if (tip.parentNode) fadeOut(tip)
+      clearActivity()
     }
     var mouseOnTip = false, old = false;
     CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
     CodeMirror.on(tip, "mouseout", function(e) {
       if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
         if (old) clear();
         else mouseOnTip = false;
       }
     });
     setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700);
-    cm.on("cursorActivity", clear);
-    cm.on('blur', clear);
-    cm.on('scroll', clear);
+    var clearActivity = onEditorActivity(cm, clear)
+  }
+
+  function onEditorActivity(cm, f) {
+    cm.on("cursorActivity", f)
+    cm.on("blur", f)
+    cm.on("scroll", f)
+    cm.on("setDoc", f)
+    return function() {
+      cm.off("cursorActivity", f)
+      cm.off("blur", f)
+      cm.off("scroll", f)
+      cm.off("setDoc", f)
+    }
   }
 
   function makeTooltip(x, y, content) {
     var node = elt("div", cls + "tooltip", content);
     node.style.left = x + "px";
     node.style.top = y + "px";
     document.body.appendChild(node);
     return node;
@@ -645,17 +657,21 @@
   function showError(ts, cm, msg) {
     if (ts.options.showError)
       ts.options.showError(cm, msg);
     else
       tempTooltip(cm, String(msg), ts);
   }
 
   function closeArgHints(ts) {
-    if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+    if (ts.activeArgHints) {
+      if (ts.activeArgHints.clear) ts.activeArgHints.clear()
+      remove(ts.activeArgHints)
+      ts.activeArgHints = null
+    }
   }
 
   function docValue(ts, doc) {
     var val = doc.doc.getValue();
     if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
     return val;
   }
 
--- a/devtools/client/sourceeditor/codemirror/codemirror.bundle.js
+++ b/devtools/client/sourceeditor/codemirror/codemirror.bundle.js
@@ -7017,25 +7017,25 @@ var CodeMirror =
 	  ); },
 	  goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },
 	    {origin: "+move", bias: 1}
 	  ); },
 	  goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },
 	    {origin: "+move", bias: -1}
 	  ); },
 	  goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {
-	    var top = cm.charCoords(range.head, "div").top + 5
+	    var top = cm.cursorCoords(range.head, "div").top + 5
 	    return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
 	  }, sel_move); },
 	  goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {
-	    var top = cm.charCoords(range.head, "div").top + 5
+	    var top = cm.cursorCoords(range.head, "div").top + 5
 	    return cm.coordsChar({left: 0, top: top}, "div")
 	  }, sel_move); },
 	  goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {
-	    var top = cm.charCoords(range.head, "div").top + 5
+	    var top = cm.cursorCoords(range.head, "div").top + 5
 	    var pos = cm.coordsChar({left: 0, top: top}, "div")
 	    if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) }
 	    return pos
 	  }, sel_move); },
 	  goLineUp: function (cm) { return cm.moveV(-1, "line"); },
 	  goLineDown: function (cm) { return cm.moveV(1, "line"); },
 	  goPageUp: function (cm) { return cm.moveV(-1, "page"); },
 	  goPageDown: function (cm) { return cm.moveV(1, "page"); },
@@ -8580,16 +8580,18 @@ var CodeMirror =
 	          { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } }
 	        ++lineNo
 	      })
 	      this.curOp.forceUpdate = true
 	      signal(this, "refresh", this)
 	    }),
 
 	    operation: function(f){return runInOp(this, f)},
+	    startOperation: function(){return startOperation(this)},
+	    endOperation: function(){return endOperation(this)},
 
 	    refresh: methodOp(function() {
 	      var oldHeight = this.display.cachedTextHeight
 	      regChange(this)
 	      this.curOp.forceUpdate = true
 	      clearCaches(this)
 	      scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop)
 	      updateGutterSpace(this)
@@ -9232,19 +9234,16 @@ var CodeMirror =
 	  this.prevInput = ""
 
 	  // Flag that indicates whether we expect input to appear real soon
 	  // now (after some event like 'keypress' or 'input') and are
 	  // polling intensively.
 	  this.pollingFast = false
 	  // Self-resetting timeout for the poller
 	  this.polling = new Delayed()
-	  // Tracks when input.reset has punted to just putting a short
-	  // string into the textarea instead of the full selection.
-	  this.inaccurateSelection = false
 	  // Used to work around IE issue with selection being forgotten when focus moves away from textarea
 	  this.hasSelection = false
 	  this.composing = null
 	};
 
 	TextareaInput.prototype.init = function (display) {
 	    var this$1 = this;
 
@@ -9271,22 +9270,16 @@ var CodeMirror =
 	    cm.state.pasteIncoming = true
 	    input.fastPoll()
 	  })
 
 	  function prepareCopyCut(e) {
 	    if (signalDOMEvent(cm, e)) { return }
 	    if (cm.somethingSelected()) {
 	      setLastCopied({lineWise: false, text: cm.getSelections()})
-	      if (input.inaccurateSelection) {
-	        input.prevInput = ""
-	        input.inaccurateSelection = false
-	        te.value = lastCopied.text.join("\n")
-	        selectInput(te)
-	      }
 	    } else if (!cm.options.lineWiseCopyCut) {
 	      return
 	    } else {
 	      var ranges = copyableRanges(cm)
 	      setLastCopied({lineWise: true, text: ranges.text})
 	      if (e.type == "cut") {
 	        cm.setSelections(ranges.ranges, null, sel_dontScroll)
 	      } else {
@@ -9355,31 +9348,27 @@ var CodeMirror =
 	    this.wrapper.style.left = drawn.teLeft + "px"
 	  }
 	};
 
 	// Reset the input to correspond to the selection (or to be empty,
 	// when not typing and nothing is selected)
 	TextareaInput.prototype.reset = function (typing) {
 	  if (this.contextMenuPending || this.composing) { return }
-	  var minimal, selected, cm = this.cm, doc = cm.doc
+	  var cm = this.cm
 	  if (cm.somethingSelected()) {
 	    this.prevInput = ""
-	    var range = doc.sel.primary()
-	    minimal = hasCopyEvent &&
-	      (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000)
-	    var content = minimal ? "-" : selected || cm.getSelection()
+	    var content = cm.getSelection()
 	    this.textarea.value = content
 	    if (cm.state.focused) { selectInput(this.textarea) }
 	    if (ie && ie_version >= 9) { this.hasSelection = content }
 	  } else if (!typing) {
 	    this.prevInput = this.textarea.value = ""
 	    if (ie && ie_version >= 9) { this.hasSelection = null }
 	  }
-	  this.inaccurateSelection = minimal
 	};
 
 	TextareaInput.prototype.getField = function () { return this.textarea };
 
 	TextareaInput.prototype.supportsTouch = function () { return false };
 
 	TextareaInput.prototype.focus = function () {
 	  if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
@@ -9720,17 +9709,17 @@ var CodeMirror =
 	CodeMirror.defineDocExtension = function (name, func) {
 	  Doc.prototype[name] = func
 	}
 
 	CodeMirror.fromTextArea = fromTextArea
 
 	addLegacyProps(CodeMirror)
 
-	CodeMirror.version = "5.27.4"
+	CodeMirror.version = "5.28.0"
 
 	return CodeMirror;
 
 	})));
 
 /***/ }),
 /* 3 */
 /***/ (function(module, exports, __webpack_require__) {
@@ -9860,21 +9849,23 @@ var CodeMirror =
 	    doFold = function(str) { return str.toLowerCase() }
 	    noFold = function(str) { return str }
 	  }
 
 	  // Maps a position in a case-folded line back to a position in the original line
 	  // (compensating for codepoints increasing in number during folding)
 	  function adjustPos(orig, folded, pos, foldFunc) {
 	    if (orig.length == folded.length) return pos
-	    for (var pos1 = Math.min(pos, orig.length);;) {
-	      var len1 = foldFunc(orig.slice(0, pos1)).length
-	      if (len1 < pos) ++pos1
-	      else if (len1 > pos) --pos1
-	      else return pos1
+	    for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
+	      if (min == max) return min
+	      var mid = (min + max) >> 1
+	      var len = foldFunc(orig.slice(0, mid)).length
+	      if (len == pos) return mid
+	      else if (len > pos) max = mid
+	      else min = mid + 1
 	    }
 	  }
 
 	  function searchStringForward(doc, query, start, caseFold) {
 	    // Empty string would match anything and never progress, so we
 	    // define it to match nothing instead.
 	    if (!query.length) return null
 	    var fold = caseFold ? doFold : noFold
@@ -20677,16 +20668,33 @@ var CodeMirror =
 	      if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to()))
 	        return CodeMirror.Pass
 	      cm.addSelection(cur.from(), cur.to());
 	    }
 	    if (fullWord)
 	      cm.state.sublimeFindFullWord = cm.doc.sel;
 	  };
 
+	  function addCursorToSelection(cm, dir) {
+	    var ranges = cm.listSelections(), newRanges = [];
+	    for (var i = 0; i < ranges.length; i++) {
+	      var range = ranges[i];
+	      var newAnchor = cm.findPosV(range.anchor, dir, "line");
+	      var newHead = cm.findPosV(range.head, dir, "line");
+	      var newRange = {anchor: newAnchor, head: newHead};
+	      newRanges.push(range);
+	      newRanges.push(newRange);
+	    }
+	    cm.setSelections(newRanges);
+	  }
+
+	  var addCursorToLineCombo = mac ? "Shift-Cmd" : 'Alt-Ctrl';
+	  cmds[map[addCursorToLineCombo + "Up"] = "addCursorToPrevLine"] = function(cm) { addCursorToSelection(cm, -1); };
+	  cmds[map[addCursorToLineCombo + "Down"] = "addCursorToNextLine"] = function(cm) { addCursorToSelection(cm, 1); };
+
 	  function isSelectedRange(ranges, from, to) {
 	    for (var i = 0; i < ranges.length; i++)
 	      if (ranges[i].from() == from && ranges[i].to() == to) return true
 	    return false
 	  }
 
 	  var mirror = "(){}[]";
 	  function selectBetweenBrackets(cm) {
@@ -21179,16 +21187,18 @@ var CodeMirror =
 
 	  function makeWidget(cm, options) {
 	    var widget = getOption(cm, options, "widget");
 	    if (typeof widget == "string") {
 	      var text = document.createTextNode(widget);
 	      widget = document.createElement("span");
 	      widget.appendChild(text);
 	      widget.className = "CodeMirror-foldmarker";
+	    } else if (widget) {
+	      widget = widget.cloneNode(true)
 	    }
 	    return widget;
 	  }
 
 	  // Clumsy backwards-compatible interface
 	  CodeMirror.newFoldFunction = function(rangeFinder, widget) {
 	    return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
 	  };
--- a/devtools/client/sourceeditor/codemirror/keymap/sublime.js
+++ b/devtools/client/sourceeditor/codemirror/keymap/sublime.js
@@ -160,16 +160,33 @@
       if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to()))
         return CodeMirror.Pass
       cm.addSelection(cur.from(), cur.to());
     }
     if (fullWord)
       cm.state.sublimeFindFullWord = cm.doc.sel;
   };
 
+  function addCursorToSelection(cm, dir) {
+    var ranges = cm.listSelections(), newRanges = [];
+    for (var i = 0; i < ranges.length; i++) {
+      var range = ranges[i];
+      var newAnchor = cm.findPosV(range.anchor, dir, "line");
+      var newHead = cm.findPosV(range.head, dir, "line");
+      var newRange = {anchor: newAnchor, head: newHead};
+      newRanges.push(range);
+      newRanges.push(newRange);
+    }
+    cm.setSelections(newRanges);
+  }
+
+  var addCursorToLineCombo = mac ? "Shift-Cmd" : 'Alt-Ctrl';
+  cmds[map[addCursorToLineCombo + "Up"] = "addCursorToPrevLine"] = function(cm) { addCursorToSelection(cm, -1); };
+  cmds[map[addCursorToLineCombo + "Down"] = "addCursorToNextLine"] = function(cm) { addCursorToSelection(cm, 1); };
+
   function isSelectedRange(ranges, from, to) {
     for (var i = 0; i < ranges.length; i++)
       if (ranges[i].from() == from && ranges[i].to() == to) return true
     return false
   }
 
   var mirror = "(){}[]";
   function selectBetweenBrackets(cm) {
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
@@ -6775,25 +6775,25 @@ var commands = {
   ); },
   goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },
     {origin: "+move", bias: 1}
   ); },
   goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },
     {origin: "+move", bias: -1}
   ); },
   goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {
-    var top = cm.charCoords(range.head, "div").top + 5
+    var top = cm.cursorCoords(range.head, "div").top + 5
     return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")
   }, sel_move); },
   goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {
-    var top = cm.charCoords(range.head, "div").top + 5
+    var top = cm.cursorCoords(range.head, "div").top + 5
     return cm.coordsChar({left: 0, top: top}, "div")
   }, sel_move); },
   goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {
-    var top = cm.charCoords(range.head, "div").top + 5
+    var top = cm.cursorCoords(range.head, "div").top + 5
     var pos = cm.coordsChar({left: 0, top: top}, "div")
     if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) }
     return pos
   }, sel_move); },
   goLineUp: function (cm) { return cm.moveV(-1, "line"); },
   goLineDown: function (cm) { return cm.moveV(1, "line"); },
   goPageUp: function (cm) { return cm.moveV(-1, "page"); },
   goPageDown: function (cm) { return cm.moveV(1, "page"); },
@@ -8338,16 +8338,18 @@ function addEditorMethods(CodeMirror) {
           { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } }
         ++lineNo
       })
       this.curOp.forceUpdate = true
       signal(this, "refresh", this)
     }),
 
     operation: function(f){return runInOp(this, f)},
+    startOperation: function(){return startOperation(this)},
+    endOperation: function(){return endOperation(this)},
 
     refresh: methodOp(function() {
       var oldHeight = this.display.cachedTextHeight
       regChange(this)
       this.curOp.forceUpdate = true
       clearCaches(this)
       scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop)
       updateGutterSpace(this)
@@ -8990,19 +8992,16 @@ var TextareaInput = function(cm) {
   this.prevInput = ""
 
   // Flag that indicates whether we expect input to appear real soon
   // now (after some event like 'keypress' or 'input') and are
   // polling intensively.
   this.pollingFast = false
   // Self-resetting timeout for the poller
   this.polling = new Delayed()
-  // Tracks when input.reset has punted to just putting a short
-  // string into the textarea instead of the full selection.
-  this.inaccurateSelection = false
   // Used to work around IE issue with selection being forgotten when focus moves away from textarea
   this.hasSelection = false
   this.composing = null
 };
 
 TextareaInput.prototype.init = function (display) {
     var this$1 = this;
 
@@ -9029,22 +9028,16 @@ TextareaInput.prototype.init = function 
     cm.state.pasteIncoming = true
     input.fastPoll()
   })
 
   function prepareCopyCut(e) {
     if (signalDOMEvent(cm, e)) { return }
     if (cm.somethingSelected()) {
       setLastCopied({lineWise: false, text: cm.getSelections()})
-      if (input.inaccurateSelection) {
-        input.prevInput = ""
-        input.inaccurateSelection = false
-        te.value = lastCopied.text.join("\n")
-        selectInput(te)
-      }
     } else if (!cm.options.lineWiseCopyCut) {
       return
     } else {
       var ranges = copyableRanges(cm)
       setLastCopied({lineWise: true, text: ranges.text})
       if (e.type == "cut") {
         cm.setSelections(ranges.ranges, null, sel_dontScroll)
       } else {
@@ -9113,31 +9106,27 @@ TextareaInput.prototype.showSelection = 
     this.wrapper.style.left = drawn.teLeft + "px"
   }
 };
 
 // Reset the input to correspond to the selection (or to be empty,
 // when not typing and nothing is selected)
 TextareaInput.prototype.reset = function (typing) {
   if (this.contextMenuPending || this.composing) { return }
-  var minimal, selected, cm = this.cm, doc = cm.doc
+  var cm = this.cm
   if (cm.somethingSelected()) {
     this.prevInput = ""
-    var range = doc.sel.primary()
-    minimal = hasCopyEvent &&
-      (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000)
-    var content = minimal ? "-" : selected || cm.getSelection()
+    var content = cm.getSelection()
     this.textarea.value = content
     if (cm.state.focused) { selectInput(this.textarea) }
     if (ie && ie_version >= 9) { this.hasSelection = content }
   } else if (!typing) {
     this.prevInput = this.textarea.value = ""
     if (ie && ie_version >= 9) { this.hasSelection = null }
   }
-  this.inaccurateSelection = minimal
 };
 
 TextareaInput.prototype.getField = function () { return this.textarea };
 
 TextareaInput.prototype.supportsTouch = function () { return false };
 
 TextareaInput.prototype.focus = function () {
   if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
@@ -9478,13 +9467,13 @@ CodeMirror.defineExtension = function (n
 CodeMirror.defineDocExtension = function (name, func) {
   Doc.prototype[name] = func
 }
 
 CodeMirror.fromTextArea = fromTextArea
 
 addLegacyProps(CodeMirror)
 
-CodeMirror.version = "5.27.4"
+CodeMirror.version = "5.28.0"
 
 return CodeMirror;
 
 })));
\ No newline at end of file
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -457,17 +457,19 @@ Editor.prototype = {
   },
 
   /**
    * Replaces the current document with a new source document
    */
   replaceDocument: function (doc) {
     let cm = editors.get(this);
     cm.swapDoc(doc);
-    this._updateLineNumberFormat();
+    if (!Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
+      this._updateLineNumberFormat();
+    }
   },
 
   /**
    * Changes the value of a currently used highlighting mode.
    * See Editor.modes for the list of all supported modes.
    */
   setMode: function (value) {
     this.setOption("mode", value);
@@ -563,17 +565,20 @@ Editor.prototype = {
         lines.push(";; .... text is truncated due to the size");
       }
       if (!done) {
         lines.push(";; .... possible error during wast conversion");
       }
       // cm will try to split into lines anyway, saving memory
       value = { split: () => lines };
     }
-    this._updateLineNumberFormat();
+
+    if (!Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
+      this._updateLineNumberFormat();
+    }
 
     cm.setValue(value);
 
     this.resetIndentUnit();
   },
 
   /**
    * Reloads the state of the editor based on all current preferences.
--- a/devtools/client/sourceeditor/test/codemirror/search_test.js
+++ b/devtools/client/sourceeditor/test/codemirror/search_test.js
@@ -69,9 +69,17 @@
     is(+new Date - t0 < 100)
   })
 
   test("expandingCaseFold", function() {
     var doc = new CodeMirror.Doc("<b>İİ İİ</b>\n<b>uu uu</b>")
     run(doc, "</b>", true, 0, 8, 0, 12, 1, 8, 1, 12);
     run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8);
   });
+
+  test("normalize", function() {
+    if (!String.prototype.normalize) return
+    var doc = new CodeMirror.Doc("yılbaşı\n수 있을까\nLe taux d'humidité à London")
+    run(doc, "s", false, 0, 5, 0, 6)
+    run(doc, "이", false, 1, 2, 1, 3)
+    run(doc, "a", false, 0, 4, 0, 5, 2, 4, 2, 5, 2, 19, 2, 20)
+  })
 })();
--- a/devtools/client/sourceeditor/test/codemirror/test.js
+++ b/devtools/client/sourceeditor/test/codemirror/test.js
@@ -1587,34 +1587,34 @@ testCM("lineWidgetChanged", function(cm)
     // If the widget is measured at a width much narrower than it is displayed at, the underHalf children will span two lines and break the test.
     // If the widget is measured at a width much wider than it is displayed at, the overHalf children will combine and break the test.
     // Note that this test only checks widgets where coverGutter is true, because these require extra styling to get the width right.
     // It may also be worthwhile to check this for non-coverGutter widgets.
     // Visually:
     // Good:
     // | ------------- display width ------------- |
     // | ------- widget-width when measured ------ |
-    // | | -- under-half -- | | -- under-half -- | | 
+    // | | -- under-half -- | | -- under-half -- | |
     // | | --- over-half --- |                     |
     // | | --- over-half --- |                     |
     // Height: measured as 3 lines, same as it will be when actually displayed
 
     // Bad (too narrow):
     // | ------------- display width ------------- |
     // | ------ widget-width when measured ----- |  < -- uh oh
     // | | -- under-half -- |                    |
     // | | -- under-half -- |                    |  < -- when measured, shoved to next line
     // | | --- over-half --- |                   |
     // | | --- over-half --- |                   |
     // Height: measured as 4 lines, more than expected . Will be displayed as 3 lines!
 
     // Bad (too wide):
     // | ------------- display width ------------- |
     // | -------- widget-width when measured ------- | < -- uh oh
-    // | | -- under-half -- | | -- under-half -- |   | 
+    // | | -- under-half -- | | -- under-half -- |   |
     // | | --- over-half --- | | --- over-half --- | | < -- when measured, combined on one line
     // Height: measured as 2 lines, less than expected. Will be displayed as 3 lines!
 
     var barelyUnderHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(285 - halfScrollbarWidth)+'px;"></div>';
     var barelyOverHalfWidthHtml = '<div style="display: inline-block; height: 1px; width: '+(305 - halfScrollbarWidth)+'px;"></div>';
     node.innerHTML = new Array(3).join(barelyUnderHalfWidthHtml) + new Array(3).join(barelyOverHalfWidthHtml);
     node.style.cssText = "background: yellow;font-size:0;line-height: " + (expectedWidgetHeight/expectedLinesInWidget) + "px;";
     return node;
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -447,17 +447,18 @@
   height: 100%;
 }
 
 .ruleview-overridden-item:last-child:after {
   display: none;
 }
 
 .ruleview-grid,
-.ruleview-swatch {
+.ruleview-swatch,
+.ruleview-shape {
   cursor: pointer;
   border-radius: 50%;
   width: 1em;
   height: 1em;
   vertical-align: middle;
   /* align the swatch with its value */
   margin-top: -1px;
   margin-inline-end: 5px;
@@ -465,16 +466,26 @@
   position: relative;
 }
 
 .ruleview-grid {
   background: url("chrome://devtools/skin/images/grid.svg");
   border-radius: 0;
 }
 
+.ruleview-shape {
+  background: url("chrome://devtools/skin/images/tool-shadereditor.svg");
+  border-radius: 0;
+  background-size: 1em;
+}
+
+.ruleview-shape-point.active {
+  background-color: var(--rule-highlight-background-color);
+}
+
 .ruleview-colorswatch::before {
   content: '';
   background-color: #eee;
   background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
                     linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
   background-size: 12px 12px;
   background-position: 0 0, 6px 6px;
   position: absolute;
@@ -591,17 +602,18 @@
 }
 
 .ruleview-selectorhighlighter:hover {
   filter: url(images/filters.svg#checked-icon-state);
 }
 
 .ruleview-grid.active,
 .ruleview-selectorhighlighter:active,
-.ruleview-selectorhighlighter.highlighted {
+.ruleview-selectorhighlighter.highlighted,
+.ruleview-shape.active {
   filter: url(images/filters.svg#checked-icon-state) brightness(0.9);
 }
 
 #ruleview-add-rule-button::before {
   background-image: url("chrome://devtools/skin/images/add.svg");
 }
 
 #pseudo-class-panel-toggle::before {
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -73,16 +73,18 @@
   /* Tooltips */
   --theme-tooltip-border: #d9e1e8;
   --theme-tooltip-background: rgba(255, 255, 255, .9);
   --theme-tooltip-shadow: rgba(155, 155, 155, 0.26);
 
   /* Command line */
   --theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme);
   --theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme-focus);
+
+  --theme-codemirror-gutter-background: #f4f4f4;
 }
 
 :root.theme-dark {
   --theme-body-background: #393f4c;
   --theme-sidebar-background: #393f4c;
   --theme-contrast-background: #ffb35b;
 
   --theme-tab-toolbar-background: #272b35;
@@ -139,16 +141,18 @@
   /* Tooltips */
   --theme-tooltip-border: #434850;
   --theme-tooltip-background: rgba(19, 28, 38, .9);
   --theme-tooltip-shadow: rgba(25, 25, 25, 0.76);
 
   /* Command line */
   --theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme);
   --theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme-focus);
+
+  --theme-codemirror-gutter-background: #262b37;
 }
 
 :root.theme-firebug {
   --theme-body-background: #fff;
   --theme-sidebar-background: #fcfcfc;
   --theme-contrast-background: #e6b064;
 
   --theme-tab-toolbar-background: rgb(240, 240, 240) linear-gradient(rgba(255, 255, 255, 0.8), transparent);
@@ -206,16 +210,18 @@
 
   /* Toolbar buttons */
   --toolbarbutton-background: transparent linear-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.2)) no-repeat;
   --toolbarbutton-hover-background: transparent;
   --toolbarbutton-hover-border-color: var(--theme-splitter-color);
   --toolbarbutton-checked-background: linear-gradient(rgba(0, 0, 0, 0.1), transparent);
   --toolbarbutton-checked-color: var(--theme-body-color);
   --toolbarbutton-checked-border-color: var(--toolbarbutton-hover-border-color);
+
+  --theme-codemirror-gutter-background: #ebeced;
 }
 
 :root.theme-firebug[platform="win"] {
   --theme-tab-toolbar-background: #d8eaf9 linear-gradient(rgba(253, 253, 253, 0.2), rgba(253, 253, 253, 0));
   --theme-toolbar-background: #d8eaf9 linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.2));
   --theme-toolbar-tab-selected-background: rgb(247, 251, 254);
   --theme-splitter-color: #aabccf;
 }
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -608,8 +608,12 @@
   stroke: var(--highlighter-guide-color);
   shape-rendering: geometricPrecision;
   vector-effect: non-scaling-stroke;
 }
 
 :-moz-native-anonymous .shapes-markers {
   fill: var(--highlighter-marker-color);
 }
+
+:-moz-native-anonymous .shapes-marker-hover {
+  fill: var(--highlighter-guide-color);
+}
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -454,16 +454,19 @@ exports.CustomHighlighterActor = protoco
     }
 
     // The assumption is that all custom highlighters need the canvasframe
     // container to append their elements, so if this is a XUL window, bail out.
     if (!isXUL(this._inspector.tabActor.window)) {
       this._highlighterEnv = new HighlighterEnvironment();
       this._highlighterEnv.initFromTabActor(inspector.tabActor);
       this._highlighter = new constructor(this._highlighterEnv);
+      if (this._highlighter.on) {
+        this._highlighter.on("highlighter-event", this._onHighlighterEvent.bind(this));
+      }
     } else {
       throw new Error("Custom " + typeName +
         "highlighter cannot be created in a XUL window");
     }
   },
 
   get conn() {
     return this._inspector && this._inspector.conn;
@@ -507,21 +510,31 @@ exports.CustomHighlighterActor = protoco
    */
   hide: function () {
     if (this._highlighter) {
       this._highlighter.hide();
     }
   },
 
   /**
+   * Upon receiving an event from the highlighter, forward it to the client.
+   */
+  _onHighlighterEvent: function (type, data) {
+    events.emit(this, "highlighter-event", data);
+  },
+
+  /**
    * Kill this actor. This method is called automatically just before the actor
    * is destroyed.
    */
   finalize: function () {
     if (this._highlighter) {
+      if (this._highlighter.off) {
+        this._highlighter.off("highlighter-event", this._onHighlighterEvent.bind(this));
+      }
       this._highlighter.destroy();
       this._highlighter = null;
     }
 
     if (this._highlighterEnv) {
       this._highlighterEnv.destroy();
       this._highlighterEnv = null;
     }
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -10,43 +10,49 @@ const { setIgnoreLayoutChanges, getCurre
 const { AutoRefreshHighlighter } = require("./auto-refresh");
 const {
   getDistance,
   clickedOnEllipseEdge,
   distanceToLine,
   projection,
   clickedOnPoint
 } = require("devtools/server/actors/utils/shapes-geometry-utils");
+const EventEmitter = require("devtools/shared/event-emitter");
 
 const BASE_MARKER_SIZE = 10;
 // the width of the area around highlighter lines that can be clicked, in px
 const LINE_CLICK_WIDTH = 5;
 const DOM_EVENTS = ["mousedown", "mousemove", "mouseup", "dblclick"];
 const _dragging = Symbol("shapes/dragging");
 
 /**
  * The ShapesHighlighter draws an outline shapes in the page.
  * The idea is to have something that is able to wrap complex shapes for css properties
  * such as shape-outside/inside, clip-path but also SVG elements.
  */
 class ShapesHighlighter extends AutoRefreshHighlighter {
   constructor(highlighterEnv) {
     super(highlighterEnv);
+    EventEmitter.decorate(this);
 
     this.ID_CLASS_PREFIX = "shapes-";
 
     this.referenceBox = "border";
     this.useStrokeBox = false;
     this.geometryBox = "";
+    this.hoveredPoint = null;
+    this.fillRule = "";
 
     this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
       this._buildMarkup.bind(this));
+    this.onPageHide = this.onPageHide.bind(this);
 
     let { pageListenerTarget } = this.highlighterEnv;
     DOM_EVENTS.forEach(event => pageListenerTarget.addEventListener(event, this));
+    pageListenerTarget.addEventListener("pagehide", this.onPageHide);
   }
 
   _buildMarkup() {
     let container = createNode(this.win, {
       attributes: {
         "class": "highlighter-container"
       }
     });
@@ -115,16 +121,27 @@ class ShapesHighlighter extends AutoRefr
       parent: mainSvg,
       attributes: {
         "id": "markers",
         "class": "markers",
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
+    createSVGNode(this.win, {
+      nodeType: "path",
+      parent: mainSvg,
+      attributes: {
+        "id": "marker-hover",
+        "class": "marker-hover",
+        "hidden": true
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
     return container;
   }
 
   get currentDimensions() {
     let { top, left, width, height } = this.currentQuads[this.referenceBox][0].bounds;
 
     // If an SVG element has a stroke, currentQuads will return the stroke bounding box.
     // However, clip-path always uses the object bounding box unless "stroke-box" is
@@ -194,16 +211,17 @@ class ShapesHighlighter extends AutoRefr
           if (this.property === "shape-outside") {
             this.currentNode.style.setProperty("width", this[_dragging].origWidth);
           }
           this[_dragging] = null;
         }
         break;
       case "mousemove":
         if (!this[_dragging]) {
+          this._handleMouseMoveNotDragging(pageX, pageY);
           return;
         }
         event.stopPropagation();
         event.preventDefault();
 
         let { point } = this[_dragging];
         if (this.shapeType === "polygon") {
           this._handlePolygonMove(pageX, pageY);
@@ -213,17 +231,17 @@ class ShapesHighlighter extends AutoRefr
           this._handleEllipseMove(point, pageX, pageY);
         } else if (this.shapeType === "inset") {
           this._handleInsetMove(point, pageX, pageY);
         }
         break;
       case "dblclick":
         if (this.shapeType === "polygon") {
           let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
-          let index = this.getPolygonClickedPoint(percentX, percentY);
+          let index = this.getPolygonPointAt(percentX, percentY);
           if (index === -1) {
             this.getPolygonClickedLine(percentX, percentY);
             return;
           }
 
           this._deletePolygonPoint(index);
         }
         break;
@@ -233,17 +251,17 @@ class ShapesHighlighter extends AutoRefr
   /**
    * Handle a click when highlighting a polygon.
    * @param {any} pageX the x coordinate of the click
    * @param {any} pageY the y coordinate of the click
    */
   _handlePolygonClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
-    let point = this.getPolygonClickedPoint(percentX, percentY);
+    let point = this.getPolygonPointAt(percentX, percentY);
     if (point === -1) {
       return;
     }
 
     let [x, y] = this.coordUnits[point];
     let xComputed = this.coordinates[point][0] / 100 * width;
     let yComputed = this.coordinates[point][1] / 100 * height;
     let unitX = getUnit(x);
@@ -266,66 +284,73 @@ class ShapesHighlighter extends AutoRefr
    */
   _handlePolygonMove(pageX, pageY) {
     let { point, unitX, unitY, valueX, valueY, ratioX, ratioY, x, y } = this[_dragging];
     let deltaX = (pageX - x) * ratioX;
     let deltaY = (pageY - y) * ratioY;
     let newX = `${valueX + deltaX}${unitX}`;
     let newY = `${valueY + deltaY}${unitY}`;
 
-    let polygonDef = this.coordUnits.map((coords, i) => {
+    let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
+    polygonDef += this.coordUnits.map((coords, i) => {
       return (i === point) ? `${newX} ${newY}` : `${coords[0]} ${coords[1]}`;
     }).join(", ");
     polygonDef = (this.geometryBox) ? `polygon(${polygonDef}) ${this.geometryBox}` :
                                       `polygon(${polygonDef})`;
 
     this.currentNode.style.setProperty(this.property, polygonDef, "important");
   }
 
   /**
    * Set the inline style of the polygon, adding a new point.
    * @param {Number} after the index of the point that the new point should be added after
    * @param {Number} x the x coordinate of the new point
    * @param {Number} y the y coordinate of the new point
    */
   _addPolygonPoint(after, x, y) {
-    let polygonDef = this.coordUnits.map((coords, i) => {
+    let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
+    polygonDef += this.coordUnits.map((coords, i) => {
       return (i === after) ? `${coords[0]} ${coords[1]}, ${x}% ${y}%` :
                              `${coords[0]} ${coords[1]}`;
     }).join(", ");
     polygonDef = (this.geometryBox) ? `polygon(${polygonDef}) ${this.geometryBox}` :
                                       `polygon(${polygonDef})`;
 
+    this.hoveredPoint = after + 1;
+    this._emitHoverEvent(this.hoveredPoint);
     this.currentNode.style.setProperty(this.property, polygonDef, "important");
   }
 
   /**
    * Set the inline style of the polygon, deleting the given point.
    * @param {Number} point the index of the point to delete
    */
   _deletePolygonPoint(point) {
     let coordinates = this.coordUnits.slice();
     coordinates.splice(point, 1);
-    let polygonDef = coordinates.map((coords, i) => {
+    let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
+    polygonDef += coordinates.map((coords, i) => {
       return `${coords[0]} ${coords[1]}`;
     }).join(", ");
     polygonDef = (this.geometryBox) ? `polygon(${polygonDef}) ${this.geometryBox}` :
                                       `polygon(${polygonDef})`;
 
+    this.hoveredPoint = null;
+    this._emitHoverEvent(this.hoveredPoint);
     this.currentNode.style.setProperty(this.property, polygonDef, "important");
   }
   /**
    * Handle a click when highlighting a circle.
    * @param {any} pageX the x coordinate of the click
    * @param {any} pageY the y coordinate of the click
    */
   _handleCircleClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
-    let point = this.getCircleClickedPoint(percentX, percentY);
+    let point = this.getCirclePointAt(percentX, percentY);
     if (!point) {
       return;
     }
 
     if (point === "center") {
       let { cx, cy } = this.coordUnits;
       let cxComputed = this.coordinates.cx / 100 * width;
       let cyComputed = this.coordinates.cy / 100 * height;
@@ -396,17 +421,17 @@ class ShapesHighlighter extends AutoRefr
   /**
    * Handle a click when highlighting an ellipse.
    * @param {any} pageX the x coordinate of the click
    * @param {any} pageY the y coordinate of the click
    */
   _handleEllipseClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
-    let point = this.getEllipseClickedPoint(percentX, percentY);
+    let point = this.getEllipsePointAt(percentX, percentY);
     if (!point) {
       return;
     }
 
     if (point === "center") {
       let { cx, cy } = this.coordUnits;
       let cxComputed = this.coordinates.cx / 100 * width;
       let cyComputed = this.coordinates.cy / 100 * height;
@@ -495,17 +520,17 @@ class ShapesHighlighter extends AutoRefr
   /**
    * Handle a click when highlighting an inset.
    * @param {any} pageX the x coordinate of the click
    * @param {any} pageY the y coordinate of the click
    */
   _handleInsetClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
-    let point = this.getInsetClickedPoint(percentX, percentY);
+    let point = this.getInsetPointAt(percentX, percentY);
     if (!point) {
       return;
     }
 
     let value = this.coordUnits[point];
     let size = (point === "left" || point === "right") ? width : height;
     let computedValue = this.coordinates[point] / 100 * size;
     let unit = getUnit(value);
@@ -548,16 +573,134 @@ class ShapesHighlighter extends AutoRefr
       `inset(${top} ${right} ${bottom} ${left} round ${round})` :
       `inset(${top} ${right} ${bottom} ${left})`;
 
     insetDef += (this.geometryBox) ? this.geometryBox : "";
 
     this.currentNode.style.setProperty(this.property, insetDef, "important");
   }
 
+  _handleMouseMoveNotDragging(pageX, pageY) {
+    let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
+    if (this.shapeType === "polygon") {
+      let point = this.getPolygonPointAt(percentX, percentY);
+      let oldHoveredPoint = this.hoveredPoint;
+      this.hoveredPoint = (point !== -1) ? point : null;
+      if (this.hoveredPoint !== oldHoveredPoint) {
+        this._emitHoverEvent(this.hoveredPoint);
+      }
+      this._handleMarkerHover(point);
+    } else if (this.shapeType === "circle") {
+      let point = this.getCirclePointAt(percentX, percentY);
+      let oldHoveredPoint = this.hoveredPoint;
+      this.hoveredPoint = point ? point : null;
+      if (this.hoveredPoint !== oldHoveredPoint) {
+        this._emitHoverEvent(this.hoveredPoint);
+      }
+      this._handleMarkerHover(point);
+    } else if (this.shapeType === "ellipse") {
+      let point = this.getEllipsePointAt(percentX, percentY);
+      let oldHoveredPoint = this.hoveredPoint;
+      this.hoveredPoint = point ? point : null;
+      if (this.hoveredPoint !== oldHoveredPoint) {
+        this._emitHoverEvent(this.hoveredPoint);
+      }
+      this._handleMarkerHover(point);
+    } else if (this.shapeType === "inset") {
+      let point = this.getInsetPointAt(percentX, percentY);
+      let oldHoveredPoint = this.hoveredPoint;
+      this.hoveredPoint = point ? point : null;
+      if (this.hoveredPoint !== oldHoveredPoint) {
+        this._emitHoverEvent(this.hoveredPoint);
+      }
+      this._handleMarkerHover(point);
+    }
+  }
+
+  _handleMarkerHover(point) {
+    // Hide hover marker for now, will be shown if point is a valid hover target
+    this.getElement("marker-hover").setAttribute("hidden", true);
+    if (point === null || point === undefined) {
+      return;
+    }
+
+    if (this.shapeType === "polygon") {
+      if (point === -1) {
+        return;
+      }
+      this._drawHoverMarker([this.coordinates[point]]);
+    } else if (this.shapeType === "circle") {
+      let { cx, cy, rx } = this.coordinates;
+      if (point === "radius") {
+        this._drawHoverMarker([[cx + rx, cy]]);
+      } else if (point === "center") {
+        this._drawHoverMarker([[cx, cy]]);
+      }
+    } else if (this.shapeType === "ellipse") {
+      if (point === "center") {
+        let { cx, cy } = this.coordinates;
+        this._drawHoverMarker([[cx, cy]]);
+      } else if (point === "rx") {
+        let { cx, cy, rx } = this.coordinates;
+        this._drawHoverMarker([[cx + rx, cy]]);
+      } else if (point === "ry") {
+        let { cx, cy, ry } = this.coordinates;
+        this._drawHoverMarker([[cx, cy + ry]]);
+      }
+    } else if (this.shapeType === "inset") {
+      if (!point) {
+        return;
+      }
+
+      let { top, right, bottom, left } = this.coordinates;
+      let centerX = (left + (100 - right)) / 2;
+      let centerY = (top + (100 - bottom)) / 2;
+      let points = point.split(",");
+      let coords = points.map(side => {
+        if (side === "top") {
+          return [centerX, top];
+        } else if (side === "right") {
+          return [100 - right, centerY];
+        } else if (side === "bottom") {
+          return [centerX, 100 - bottom];
+        } else if (side === "left") {
+          return [left, centerY];
+        }
+        return null;
+      });
+
+      this._drawHoverMarker(coords);
+    }
+  }
+
+  _drawHoverMarker(points) {
+    let { width, height } = this.zoomAdjustedDimensions;
+    let zoom = getCurrentZoom(this.win);
+    let path = points.map(([x, y]) => {
+      return getCirclePath(x, y, width, height, zoom);
+    }).join(" ");
+
+    let markerHover = this.getElement("marker-hover");
+    markerHover.setAttribute("d", path);
+    markerHover.removeAttribute("hidden");
+  }
+
+  _emitHoverEvent(point) {
+    if (point === null || point === undefined) {
+      this.emit("highlighter-event", {
+        type: "shape-hover-off"
+      });
+    } else {
+      this.emit("highlighter-event", {
+        type: "shape-hover-on",
+        point: point.toString()
+      });
+    }
+  }
+
   /**
    * Convert the given coordinates on the page to percentages relative to the current
    * element.
    * @param {Number} pageX the x coordinate on the page
    * @param {Number} pageY the y coordinate on the page
    * @returns {Object} object of form {percentX, percentY}, which are the x/y coords
    *          in percentages relative to the element.
    */
@@ -585,23 +728,23 @@ class ShapesHighlighter extends AutoRefr
     x = x * width / 100;
     y = y * height / 100;
     x += left;
     y += top;
     return { x, y };
   }
 
   /**
-   * Get the id of the point clicked on the polygon highlighter.
+   * Get the id of the point on the polygon highlighter at the given coordinate.
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
    * @param {Number} pageY the y coordinate on the page, in % relative to the element
    * @returns {Number} the index of the point that was clicked on in this.coordinates,
    *          or -1 if none of the points were clicked on.
    */
-  getPolygonClickedPoint(pageX, pageY) {
+  getPolygonPointAt(pageX, pageY) {
     let { coordinates } = this;
     let { width, height } = this.zoomAdjustedDimensions;
     let zoom = getCurrentZoom(this.win);
     let clickRadiusX = BASE_MARKER_SIZE / zoom * 100 / width;
     let clickRadiusY = BASE_MARKER_SIZE / zoom * 100 / height;
 
     for (let [index, coord] of coordinates.entries()) {
       let [x, y] = coord;
@@ -640,23 +783,23 @@ class ShapesHighlighter extends AutoRefr
         let [newX, newY] = projection(x1, y1, x2, y2, pageX, pageY);
         this._addPolygonPoint(i, newX, newY);
         return;
       }
     }
   }
 
   /**
-   * Check if the center point or radius of the circle highlighter was clicked
+   * Check if the center point or radius of the circle highlighter is at given coords
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
    * @param {Number} pageY the y coordinate on the page, in % relative to the element
    * @returns {String} "center" if the center point was clicked, "radius" if the radius
    *          was clicked, "" if neither was clicked.
    */
-  getCircleClickedPoint(pageX, pageY) {
+  getCirclePointAt(pageX, pageY) {
     let { cx, cy, rx, ry } = this.coordinates;
     let { width, height } = this.zoomAdjustedDimensions;
     let zoom = getCurrentZoom(this.win);
     let clickRadiusX = BASE_MARKER_SIZE / zoom * 100 / width;
     let clickRadiusY = BASE_MARKER_SIZE / zoom * 100 / height;
 
     if (clickedOnPoint(pageX, pageY, cx, cy, clickRadiusX, clickRadiusY)) {
       return "center";
@@ -668,24 +811,24 @@ class ShapesHighlighter extends AutoRefr
         clickedOnPoint(pageX, pageY, cx + rx, cy, clickRadiusX, clickRadiusY)) {
       return "radius";
     }
 
     return "";
   }
 
   /**
-   * Check if the center point or rx/ry points of the ellipse highlighter was clicked
+   * Check if the center or rx/ry points of the ellipse highlighter is at given point
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
    * @param {Number} pageY the y coordinate on the page, in % relative to the element
    * @returns {String} "center" if the center point was clicked, "rx" if the x-radius
    *          point was clicked, "ry" if the y-radius point was clicked,
    *          "" if none was clicked.
    */
-  getEllipseClickedPoint(pageX, pageY) {
+  getEllipsePointAt(pageX, pageY) {
     let { cx, cy, rx, ry } = this.coordinates;
     let { width, height } = this.zoomAdjustedDimensions;
     let zoom = getCurrentZoom(this.win);
     let clickRadiusX = BASE_MARKER_SIZE / zoom * 100 / width;
     let clickRadiusY = BASE_MARKER_SIZE / zoom * 100 / height;
 
     if (clickedOnPoint(pageX, pageY, cx, cy, clickRadiusX, clickRadiusY)) {
       return "center";
@@ -698,23 +841,23 @@ class ShapesHighlighter extends AutoRefr
     if (clickedOnPoint(pageX, pageY, cx, cy + ry, clickRadiusX, clickRadiusY)) {
       return "ry";
     }
 
     return "";
   }
 
   /**
-   * Check if the edges of the inset highlighter was clicked
+   * Check if the edges of the inset highlighter is at given coords
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
    * @param {Number} pageY the y coordinate on the page, in % relative to the element
    * @returns {String} "top", "left", "right", or "bottom" if any of those edges were
    *          clicked. "" if none were clicked.
    */
-  getInsetClickedPoint(pageX, pageY) {
+  getInsetPointAt(pageX, pageY) {
     let { top, left, right, bottom } = this.coordinates;
     let zoom = getCurrentZoom(this.win);
     let { width, height } = this.zoomAdjustedDimensions;
     let clickWidthX = LINE_CLICK_WIDTH * 100 / width;
     let clickWidthY = LINE_CLICK_WIDTH * 100 / height;
     let clickRadiusX = BASE_MARKER_SIZE / zoom * 100 / width;
     let clickRadiusY = BASE_MARKER_SIZE / zoom * 100 / height;
     let centerX = (left + (100 - right)) / 2;
@@ -806,33 +949,44 @@ class ShapesHighlighter extends AutoRefr
    * Parses the definition of the CSS polygon() function and returns its points,
    * converted to percentages.
    * @param {String} definition the arguments of the polygon() function
    * @returns {Array} an array of the points of the polygon, with all values
    *          evaluated and converted to percentages
    */
   polygonPoints(definition) {
     this.coordUnits = this.polygonRawPoints();
-    return definition.split(", ").map(coords => {
+    let splitDef = definition.split(", ");
+    if (splitDef[0] === "evenodd" || splitDef[0] === "nonzero") {
+      splitDef.shift();
+    }
+    return splitDef.map(coords => {
       return splitCoords(coords).map(this.convertCoordsToPercent.bind(this));
     });
   }
 
   /**
    * Parse the raw (non-computed) definition of the CSS polygon.
    * @returns {Array} an array of the points of the polygon, with units preserved.
    */
   polygonRawPoints() {
     let definition = getDefinedShapeProperties(this.currentNode, this.property);
     if (definition === this.rawDefinition) {
       return this.coordUnits;
     }
     this.rawDefinition = definition;
     definition = definition.substring(8, definition.lastIndexOf(")"));
-    return definition.split(", ").map(coords => {
+    let splitDef = definition.split(", ");
+    if (splitDef[0].includes("evenodd") || splitDef[0].includes("nonzero")) {
+      this.fillRule = splitDef[0].trim();
+      splitDef.shift();
+    } else {
+      this.fillRule = "";
+    }
+    return splitDef.map(coords => {
       return splitCoords(coords).map(coord => {
         // Undo the insertion of &nbsp; that was done in splitCoords.
         return coord.replace(/\u00a0/g, " ");
       });
     });
   }
 
   /**
@@ -1069,16 +1223,17 @@ class ShapesHighlighter extends AutoRefr
            this.getElement("polygon").hasAttribute("hidden") &&
            this.getElement("rect").hasAttribute("hidden");
   }
 
   /**
    * Show the highlighter on a given node
    */
   _show() {
+    this.hoveredPoint = this.options.hoverPoint;
     return this._update();
   }
 
   /**
    * The AutoRefreshHighlighter's _hasMoved method returns true only if the element's
    * quads have changed. Override it so it also returns true if the element's shape has
    * changed (which can happen when you change a CSS properties for instance).
    */
@@ -1145,16 +1300,18 @@ class ShapesHighlighter extends AutoRefr
     } else if (this.shapeType === "circle") {
       this._updateCircleShape(width, height, zoom);
     } else if (this.shapeType === "ellipse") {
       this._updateEllipseShape(width, height, zoom);
     } else if (this.shapeType === "inset") {
       this._updateInsetShape(width, height, zoom);
     }
 
+    this._handleMarkerHover(this.hoveredPoint);
+
     let { width: winWidth, height: winHeight } = this._winDimensions;
     root.removeAttribute("hidden");
     root.setAttribute("style",
       `position:absolute; width:${winWidth}px;height:${winHeight}px; overflow:hidden`);
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
 
     return true;
@@ -1257,16 +1414,24 @@ class ShapesHighlighter extends AutoRefr
   _hide() {
     setIgnoreLayoutChanges(true);
 
     this._hideShapes();
     this.getElement("markers").setAttribute("d", "");
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
   }
+
+  onPageHide({ target }) {
+    // If a page hide event is triggered for current window's highlighter, hide the
+    // highlighter.
+    if (target.defaultView === this.win) {
+      this.hide();
+    }
+  }
 }
 
 /**
  * Get the "raw" (i.e. non-computed) shape definition on the given node.
  * @param {nsIDOMNode} node the node to analyze
  * @param {String} property the CSS property for which a value should be retrieved.
  * @returns {String} the value of the given CSS property on the given node.
  */
--- a/devtools/shared/css/properties-db.js
+++ b/devtools/shared/css/properties-db.js
@@ -70,16 +70,18 @@ exports.COLOR_TAKING_FUNCTIONS = ["linea
  * Functions that accept an angle argument. This list can be manually edited.
  */
 exports.ANGLE_TAKING_FUNCTIONS = ["linear-gradient", "-moz-linear-gradient",
                                   "repeating-linear-gradient",
                                   "-moz-repeating-linear-gradient", "rotate", "rotateX",
                                   "rotateY", "rotateZ", "rotate3d", "skew", "skewX",
                                   "skewY", "hue-rotate"];
 
+exports.BASIC_SHAPE_FUNCTIONS = ["polygon", "circle", "ellipse", "inset"];
+
 /**
  * The list of all CSS Pseudo Elements.
  *
  * This list can be updated with `mach devtools-css-db`.
  */
 exports.PSEUDO_ELEMENTS = db.PSEUDO_ELEMENTS;
 
 /**
--- a/devtools/shared/specs/highlighters.js
+++ b/devtools/shared/specs/highlighters.js
@@ -33,16 +33,23 @@ const highlighterSpec = generateActorSpe
   }
 });
 
 exports.highlighterSpec = highlighterSpec;
 
 const customHighlighterSpec = generateActorSpec({
   typeName: "customhighlighter",
 
+  events: {
+    "highlighter-event": {
+      type: "highlighter-event",
+      data: Arg(0, "json")
+    }
+  },
+
   methods: {
     release: {
       release: true
     },
     show: {
       request: {
         node: Arg(0, "domnode"),
         options: Arg(1, "nullable:json")
--- a/docshell/base/crashtests/432114-2.html
+++ b/docshell/base/crashtests/432114-2.html
@@ -4,13 +4,18 @@
 </head>
 <body>
 <script>
   window.addEventListener("DOMNodeRemoved", function() {
     setTimeout(function() {
       document.documentElement.removeAttribute("class");
     }, 0);
   });
+  var iframe = document.getElementById("content");
+  iframe.onload=function() {
+    dump("iframe onload\n");
+    console.log("iframe onload");
+  };
 </script>
-<iframe id="content" src="data:application/xhtml+xml;charset=utf-8,%3Chtml%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0A%3Cframeset%20contenteditable%3D%22true%22/%3E%0A%3Cscript%3E%0Afunction%20doExecCommand%28%29%7B%0Adocument.execCommand%28%27formatBlock%27%2C%20false%2C%20%27p%27%29%3B%0A%7D%0AsetTimeout%28doExecCommand%2C100%29%3B%0Awindow.addEventListener%28%27DOMNodeRemoved%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3C/html%3E" style="width:1000px;height: 200px;"></iframe>
+<iframe id="content" src="file_432114-2.xhtml" style="width:1000px;height: 200px;"></iframe>
 
 </body>
 </html>
--- a/docshell/base/crashtests/914521.html
+++ b/docshell/base/crashtests/914521.html
@@ -15,19 +15,27 @@ function f()
     }
 
     window.addEventListener("popstate", spin);
     window.close();
     window.location = "#c";
     finish();
 }
 
+function init()
+{
+  SpecialPowers.pushPrefEnv({"set": [["security.data_uri.unique_opaque_origin", false]]}, start);
+}
+
 function start()
 {
     var html = "<script>" + f + "<\/script><body onload=f()>";
     var win = window.open("data:text/html," + encodeURIComponent(html), null, "width=300,height=300");
-    win.finish = function() { document.documentElement.removeAttribute("class"); };
+    win.finish = function() {
+      SpecialPowers.clearUserPref("security.data_uri.unique_opaque_origin");
+      document.documentElement.removeAttribute("class");
+    };
 }
 
 </script>
 </head>
-<body onload="start();"></body>
+<body onload="init();"></body>
 </html>
new file mode 100644
--- /dev/null
+++ b/docshell/base/crashtests/file_432114-2.xhtml
@@ -0,0 +1,1 @@
+<html xmlns='http://www.w3.org/1999/xhtml'><frameset contenteditable='true'/><script>function doExecCommand(){dump("doExecCommand\n");document.execCommand('formatBlock', false, 'p');}setTimeout(doExecCommand,100); window.addEventListener('DOMNodeRemoved', function() {window.frameElement.parentNode.removeChild(window.frameElement);}, true);</script></html>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10285,18 +10285,21 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   nsCOMPtr<nsIDocShellTreeItem> parent = GetParentDocshell();
   if (parent) {
     nsCOMPtr<nsIDocument> doc = parent->GetDocument();
     if (doc) {
       doc->TryCancelFrameLoaderInitialization(this);
     }
   }
 
+  bool loadFromExternal = false;
+
   // Before going any further vet loads initiated by external programs.
   if (aLoadType == LOAD_NORMAL_EXTERNAL) {
+    loadFromExternal = true;
     // Disallow external chrome: loads targetted at content windows
     bool isChrome = false;
     if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) {
       NS_WARNING("blocked external chrome: url -- use '--chrome' option");
       return NS_ERROR_FAILURE;
     }
 
     // clear the decks to prevent context bleed-through (bug 298255)
@@ -10789,17 +10792,18 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   attrs.SetFirstPartyDomain(isTopLevelDoc, aURI);
 
   net::PredictorLearn(aURI, nullptr,
                       nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
   net::PredictorPredict(aURI, nullptr,
                         nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
 
   nsCOMPtr<nsIRequest> req;
-  rv = DoURILoad(aURI, aOriginalURI, aResultPrincipalURI, aLoadReplace, aReferrer,
+  rv = DoURILoad(aURI, aOriginalURI, aResultPrincipalURI, aLoadReplace,
+                 loadFromExternal, aReferrer,
                  !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
                  aReferrerPolicy,
                  aTriggeringPrincipal, principalToInherit, aTypeHint,
                  aFileName, aPostData, aHeadersData,
                  aFirstParty, aDocShell, getter_AddRefs(req),
                  (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
                  (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0,
                  (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0,
@@ -10870,16 +10874,17 @@ nsDocShell::GetInheritedPrincipal(bool a
   return nullptr;
 }
 
 nsresult
 nsDocShell::DoURILoad(nsIURI* aURI,
                       nsIURI* aOriginalURI,
                       Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
                       bool aLoadReplace,
+                      bool aLoadFromExternal,
                       nsIURI* aReferrerURI,
                       bool aSendReferrer,
                       uint32_t aReferrerPolicy,
                       nsIPrincipal* aTriggeringPrincipal,
                       nsIPrincipal* aPrincipalToInherit,
                       const char* aTypeHint,
                       const nsAString& aFileName,
                       nsIInputStream* aPostData,
@@ -11022,35 +11027,44 @@ nsDocShell::DoURILoad(nsIURI* aURI,
 
   nsCOMPtr<nsILoadInfo> loadInfo =
     (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) ?
       new LoadInfo(loadingWindow, aTriggeringPrincipal,
                    securityFlags) :
       new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
                    securityFlags, aContentPolicyType);
 
-  if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
-    enum TopLevelDataState {
-      DATA_NAVIGATED = 0,
-      DATA_TYPED = 1,
-      NO_DATA = 2,
-    };
-    bool isDataURI = (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
-    if (isDataURI) {
-      // In all cases where the toplevel document is navigated to a data: URI
-      // the triggeringPrincipal is a CodeBasePrincipal. In all other cases
-      // e.g. typing a data: URL into the URL-Bar or also clicking a bookmark
-      // uses a SystemPrincipal as the triggeringPrincipal.
-      if (aTriggeringPrincipal->GetIsCodebasePrincipal()) {
-        Telemetry::Accumulate(Telemetry::DOCUMENT_DATA_URI_LOADS, DATA_NAVIGATED);
-      } else {
-        Telemetry::Accumulate(Telemetry::DOCUMENT_DATA_URI_LOADS, DATA_TYPED);
-      }
-    } else {
-      Telemetry::Accumulate(Telemetry::DOCUMENT_DATA_URI_LOADS, NO_DATA);
+  if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT &&
+      nsIOService::BlockToplevelDataUriNavigations()) {
+    bool isDataURI =
+      (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
+    // Let's block all toplevel document navigations to a data: URI.
+    // In all cases where the toplevel document is navigated to a
+    // data: URI the triggeringPrincipal is a codeBasePrincipal, or
+    // a NullPrincipal. In other cases, e.g. typing a data: URL into
+    // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
+    // we don't want to block those loads. Only exception, loads coming
+    // from an external applicaton (e.g. Thunderbird) don't load
+    // using a codeBasePrincipal, but we want to block those loads.
+    if (isDataURI && (aLoadFromExternal || 
+        !nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal))) {
+      NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+      if (specUTF16.Length() > 50) {
+        specUTF16.Truncate(50);
+        specUTF16.AppendLiteral("...");
+      }
+      const char16_t* params[] = { specUTF16.get() };
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
+                                      // no doc available, log to browser console
+                                      nullptr,
+                                      nsContentUtils::eSECURITY_PROPERTIES,
+                                      "BlockTopLevelDataURINavigation",
+                                      params, ArrayLength(params));
+      return NS_OK;
     }
   }
 
   if (aPrincipalToInherit) {
     loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
   }
 
   // We have to do this in case our OriginAttributes are different from the
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -372,16 +372,17 @@ protected:
   // and the contents of aSrcdoc will be loaded instead of aURI.
   // aOriginalURI will be set as the originalURI on the channel that does the
   // load. If aOriginalURI is null, aURI will be set as the originalURI.
   // If aLoadReplace is true, LOAD_REPLACE flag will be set to the nsIChannel.
   nsresult DoURILoad(nsIURI* aURI,
                      nsIURI* aOriginalURI,
                      mozilla::Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
                      bool aLoadReplace,
+                     bool aLoadFromExternal,
                      nsIURI* aReferrer,
                      bool aSendReferrer,
                      uint32_t aReferrerPolicy,
                      nsIPrincipal* aTriggeringPrincipal,
                      nsIPrincipal* aPrincipalToInherit,
                      const char* aTypeHint,
                      const nsAString& aFileName,
                      nsIInputStream* aPostData,
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1383478.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+  <head>
+    <meta charset="utf-8" />
+    <title>This browser is crashing</title>
+  </head>
+  <body>
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+<Object class="crashDroid" />
+          </body>
+</html>
--- a/dom/base/crashtests/504224.html
+++ b/dom/base/crashtests/504224.html
@@ -1,22 +1,23 @@
 <html class="reftest-wait">
 <head>
 <title>Crash [@ nsFocusManager::GetCommonAncestor], part 2</title>
 </head>
 <body>
-<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%0A%3Cbody%20onunload%3D%22window.frameElement.parentNode.removeChild%28window.frameElement%29%22%20tabindex%3D%221%22%3E%0A%3Cscript%3E%0Adocument.body.focus%28%29%3B%0A%3C/script%3E%0A%3C/body%3E%0A%3C/html%3E" id="content"></iframe>
+<iframe src="file_504224.html" id="content"></iframe>
 <script>
 var src=document.getElementById('src');
 setInterval(function() {
   if (!document.getElementById('content')) {
     var x=document.createElement('iframe');
     x.src=src;
     x.id = 'content';
     document.body.appendChild(x);
     setTimeout(function() { window.focus(); document.documentElement.removeAttribute('class'); }, 100);
-  } else
+  } else {
     window.frames[0].location.reload();
+  }
 }, 500);
 </script>
 </body>
 </html>
 
--- a/dom/base/crashtests/593302-1.html
+++ b/dom/base/crashtests/593302-1.html
@@ -19,11 +19,11 @@
         s.id = i;
         return s;
       }
 
       function remove(n)    { n.remove(); }
     </script>
   </head>
   <body onload="boom();">
-    <iframe id="iframe" src="data:text/html,S"></iframe>
+    <iframe id="iframe" srcdoc="<html>S</html>"></iframe>
   </body>
 </html>
--- a/dom/base/crashtests/851353-1.html
+++ b/dom/base/crashtests/851353-1.html
@@ -15,11 +15,11 @@
             }
 
             doc.open();
             setTimeout(runnable, 0);
         }
     </script>
 </head>
 <body onload='start()'>
-    <iframe src="data:text/html,<meta charset=UTF-8><body><video src=http://localhost:8080/ controls=true loop=true autoplay=true autobuffer=false></video>"></iframe>
+    <iframe srcdoc="<body><video src=http://localhost:8080/ controls=true loop=true autoplay=true autobuffer=false></video>"></iframe>
 </body>
 </html>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -214,8 +214,9 @@ pref(dom.IntersectionObserver.enabled,tr
 load 1370072.html
 pref(clipboard.autocopy,true) load 1370737.html
 pref(dom.IntersectionObserver.enabled,true) load 1370968.html
 load 1377826.html
 load structured_clone_container_throws.html
 HTTP(..) load xhr_abortinprogress.html
 load xhr_empty_datauri.html
 load xhr_html_nullresponse.html
+load 1383478.html
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/file_504224.html
@@ -0,0 +1,7 @@
+<html><head></head>
+  <body onunload="window.frameElement.parentNode.removeChild(window.frameElement)" tabindex="1">
+  <script>
+document.body.focus();
+  </script>
+  </body>
+</html>
--- a/dom/base/nsDOMNavigationTiming.h
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -79,21 +79,17 @@ public:
     return mLoadEventStart;
   }
   DOMTimeMilliSec GetLoadEventEnd() const
   {
     return mLoadEventEnd;
   }
   DOMTimeMilliSec GetTimeToNonBlankPaint() const
   {
-    if (mNonBlankPaintTimeStamp.IsNull()) {
-      return 0;
-    }
-
-    return TimeStampToDOMHighRes(mNonBlankPaintTimeStamp);
+    return TimeStampToDOM(mNonBlankPaintTimeStamp);
   }
 
   enum class DocShellState : uint8_t {
     eActive,
     eInactive
   };
 
   void NotifyNavigationStart(DocShellState aDocShellState);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4420,16 +4420,21 @@ void
 nsPIDOMWindowInner::SyncStateFromParentWindow()
 {
   nsGlobalWindow::Cast(this)->SyncStateFromParentWindow();
 }
 
 bool
 nsPIDOMWindowInner::IsPlayingAudio()
 {
+  for (uint32_t i = 0; i < mAudioContexts.Length(); i++) {
+    if (mAudioContexts[i]->IsRunning()) {
+      return true;
+    }
+  }
   RefPtr<AudioChannelService> acs = AudioChannelService::Get();
   if (!acs) {
     return false;
   }
   auto outer = GetOuterWindow();
   if (!outer) {
     // We've been unlinked and are about to die.  Not a good time to pretend to
     // be playing audio.
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3112,31 +3112,42 @@ nsObjectLoadingContent::LoadFallback(Fal
   nsTArray<nsINodeList*> childNodes;
   if ((thisContent->IsHTMLElement(nsGkAtoms::object) ||
        thisContent->IsHTMLElement(nsGkAtoms::applet)) &&
       (aType == eFallbackUnsupported ||
        aType == eFallbackDisabled ||
        aType == eFallbackBlocklisted ||
        aType == eFallbackAlternate))
   {
-    for (nsIContent* child = thisContent->GetFirstChild(); child;
-         child = child->GetNextNode(thisContent)) {
+    for (nsIContent* child = thisContent->GetFirstChild(); child; ) {
+      // When we advance to our next child, we don't want to traverse subtrees
+      // under descendant <object> and <embed> elements; those will handle
+      // those subtrees themselves if they end up falling back.
+      bool skipChildDescendants = false;
       if (aType != eFallbackAlternate &&
           !child->IsHTMLElement(nsGkAtoms::param) &&
           nsStyleUtil::IsSignificantChild(child, true, false)) {
         aType = eFallbackAlternate;
       }
       if (thisIsObject) {
         if (child->IsHTMLElement(nsGkAtoms::embed)) {
           HTMLSharedObjectElement* embed = static_cast<HTMLSharedObjectElement*>(child);
           embed->StartObjectLoad(true, true);
+          skipChildDescendants = true;
         } else if (auto object = HTMLObjectElement::FromContent(child)) {
           object->StartObjectLoad(true, true);
+          skipChildDescendants = true;
         }
       }
+
+      if (skipChildDescendants) {
+        child = child->GetNextNonChildNode(thisContent);
+      } else {
+        child = child->GetNextNode(thisContent);
+      }
     }
   }
 
   mFallbackType = aType;
 
   // Notify
   if (!aNotify) {
     return; // done
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   file_use_counter_outer.html
   file_use_counter_svg_getElementById.svg
   file_use_counter_svg_currentScale.svg
   file_use_counter_svg_fill_pattern_definition.svg
   file_use_counter_svg_fill_pattern.svg
   file_use_counter_svg_fill_pattern_internal.svg
   file_use_counter_svg_fill_pattern_data.svg
   file_webaudioLoop.html
+  file_webaudio_startstop.html
   plugin.js
   !/image/test/mochitest/shaver.png
 
 [browser_blocking_image.js]
 [browser_bug593387.js]
 [browser_bug902350.js]
 tags = mcb
 [browser_bug1011748.js]
--- a/dom/base/test/browser_timeout_throttling_with_audio_playback.js
+++ b/dom/base/test/browser_timeout_throttling_with_audio_playback.js
@@ -6,17 +6,17 @@ if (!gMultiProcessBrowser) {
 }
 
 const kBaseURI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
 const kPluginJS = "chrome://mochitests/content/browser/dom/base/test/plugin.js";
 var testURLs = [
   "http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html",
   "http://mochi.test:8888/browser/dom/base/test/file_audioLoopInIframe.html",
   "http://mochi.test:8888/browser/dom/base/test/file_pluginAudio.html",
-  "http://mochi.test:8888/browser/dom/base/test/file_webaudioLoop.html",
+  "http://mochi.test:8888/browser/dom/base/test/file_webaudio_startstop.html",
 ];
 
 // We want to ensure that while audio is being played back, a background tab is
 // treated the same as a foreground tab as far as timeout throttling is concerned.
 // So we use a 100,000 second minimum timeout value for background tabs.  This
 // means that in case the test fails, it will time out in practice, but just for
 // sanity the test condition ensures that the observed timeout delay falls in
 // this range.
--- a/dom/base/test/file_webaudioLoop.html
+++ b/dom/base/test/file_webaudioLoop.html
@@ -3,29 +3,20 @@
 var ac = new AudioContext();
 var runningPromise = new Promise(resolve => {
   ac.onstatechange = event => {
     if (ac.state == "running") {
       resolve();
     }
   };
 });
-fetch("audio.ogg").then(response => {
-  return response.arrayBuffer();
-}).then(ab => {
-  return ac.decodeAudioData(ab);
-}).then(ab => {
-  var src = ac.createBufferSource();
-  src.buffer = ab;
-  src.loop = true;
-  src.loopStart = 0;
-  src.loopEnd = ab.duration;
-  src.start();
-  src.connect(ac.destination);
-});
+
+var osc = ac.createOscillator();
+osc.connect(ac.destination);
+osc.start(0);
 
 var suspendPromise;
 function suspendAC() {
   runningPromise.then(() => {
     suspendPromise = ac.suspend();
   });
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_webaudio_startstop.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script>
+var ac = new AudioContext();
+var runningPromise = new Promise(resolve => {
+  ac.onstatechange = event => {
+    if (ac.state == "running") {
+      resolve();
+    }
+  };
+});
+
+var osc = ac.createOscillator();
+osc.connect(ac.destination);
+osc.start(0);
+osc.stop(osc.context.currentTime + 2.0);
+
+var suspendPromise;
+function suspendAC() {
+  runningPromise.then(() => {
+    suspendPromise = ac.suspend();
+  });
+}
+
+var resumePromise;
+function resumeAC() {
+  suspendPromise.then(() => {
+    resumePromise = ac.resume();
+  });
+}
+
+function closeAC() {
+  resumePromise.then(() => {
+    ac.close();
+  });
+}
+</script>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -205,16 +205,17 @@ support-files =
   viewport_helpers.js
   w3element_traversal.svg
   wholeTexty-helper.xml
   referrerHelper.js
   img_referrer_testserver.sjs
   file_audioLoop.html
   file_webaudioLoop.html
   file_webaudioLoop2.html
+  file_webaudio_startstop.html
   file_pluginAudio.html
   file_pluginAudioNonAutoStart.html
   noaudio.webm
   referrer_helper.js
   referrer_testserver.sjs
   script_postmessages_fileList.js
   iframe_postMessages.html
   test_anonymousContent_style_csp.html^headers^
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ActorsParent.h"
 
 #include <algorithm>
+#include <stdint.h> // UINTPTR_MAX, uintptr_t
 #include "FileInfo.h"
 #include "FileManager.h"
 #include "IDBObjectStore.h"
 #include "IDBTransaction.h"
 #include "IndexedDatabase.h"
 #include "IndexedDatabaseInlines.h"
 #include "IndexedDatabaseManager.h"
 #include "js/StructuredClone.h"
@@ -849,16 +850,21 @@ ReadCompressedIndexDataValuesFromBlob(co
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(aBlobData);
   MOZ_ASSERT(aBlobDataLength);
   MOZ_ASSERT(aIndexValues.IsEmpty());
 
   AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", STORAGE);
 
+  if (uintptr_t(aBlobData) > UINTPTR_MAX - aBlobDataLength) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_FILE_CORRUPTED;
+  }
+
   const uint8_t* blobDataIter = aBlobData;
   const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
 
   while (blobDataIter < blobDataEnd) {
     int64_t indexId;
     bool unique;
     ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
 
@@ -868,17 +874,18 @@ ReadCompressedIndexDataValuesFromBlob(co
     }
 
     // Read key buffer length.
     const uint64_t keyBufferLength =
       ReadCompressedNumber(&blobDataIter, blobDataEnd);
 
     if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
         NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
-        NS_WARN_IF(blobDataIter + keyBufferLength > blobDataEnd)) {
+        NS_WARN_IF(keyBufferLength > uintptr_t(blobDataEnd)) ||
+        NS_WARN_IF(blobDataIter > blobDataEnd - keyBufferLength)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_FILE_CORRUPTED;
     }
 
     nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
                         uint32_t(keyBufferLength));
     blobDataIter += keyBufferLength;
 
@@ -886,17 +893,18 @@ ReadCompressedIndexDataValuesFromBlob(co
 
     // Read sort key buffer length.
     const uint64_t sortKeyBufferLength =
       ReadCompressedNumber(&blobDataIter, blobDataEnd);
 
     if (sortKeyBufferLength > 0) {
       if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
           NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
-          NS_WARN_IF(blobDataIter + sortKeyBufferLength > blobDataEnd)) {
+          NS_WARN_IF(sortKeyBufferLength > uintptr_t(blobDataEnd)) ||
+          NS_WARN_IF(blobDataIter > blobDataEnd - sortKeyBufferLength)) {
         IDB_REPORT_INTERNAL_ERR();
         return NS_ERROR_FILE_CORRUPTED;
       }
 
       nsCString sortKeyBuffer(reinterpret_cast<const char*>(blobDataIter),
                               uint32_t(sortKeyBufferLength));
       blobDataIter += sortKeyBufferLength;
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1617,17 +1617,20 @@ ContentChild::RecvSetProcessSandbox(cons
       for (const nsACString& callNrString : extraSyscalls.Split(',')) {
         nsresult rv;
         int callNr = PromiseFlatCString(callNrString).ToInteger(&rv);
         if (NS_SUCCEEDED(rv)) {
           syscallWhitelist.push_back(callNr);
         }
       }
     }
-    sandboxEnabled = SetContentProcessSandbox(brokerFd, syscallWhitelist);
+    ContentChild* cc = ContentChild::GetSingleton();
+    bool isFileProcess = cc->GetRemoteType().EqualsLiteral(FILE_REMOTE_TYPE);
+    sandboxEnabled = SetContentProcessSandbox(brokerFd, isFileProcess,
+                                              syscallWhitelist);
   }
 #elif defined(XP_WIN)
   mozilla::SandboxTarget::Instance()->StartSandbox();
 #elif defined(XP_MACOSX)
   sandboxEnabled = StartMacOSContentSandbox();
 #endif
 
 #if defined(MOZ_CRASHREPORTER)
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2430,18 +2430,19 @@ ContentParent::InitInternal(ProcessPrior
   // should be changed so that it is required to restart firefox for the change
   // of value to take effect.
   shouldSandbox = (GetEffectiveContentSandboxLevel() > 0) &&
     !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX");
 
 #ifdef XP_LINUX
   if (shouldSandbox) {
     MOZ_ASSERT(!mSandboxBroker);
+    bool isFileProcess = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
     UniquePtr<SandboxBroker::Policy> policy =
-      sSandboxBrokerPolicyFactory->GetContentPolicy(Pid());
+      sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess);
     if (policy) {
       brokerFd = FileDescriptor();
       mSandboxBroker = SandboxBroker::Create(Move(policy), Pid(), brokerFd);
       if (!mSandboxBroker) {
         KillHard("SandboxBroker::Create failed");
         return;
       }
       MOZ_ASSERT(static_cast<const FileDescriptor&>(brokerFd).IsValid());
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -76,8 +76,11 @@ WeakCipherSuiteWarning=This site uses th
 
 #XCTO: nosniff
 # LOCALIZATION NOTE: Do not translate "X-Content-Type-Options: nosniff".
 MimeTypeMismatch=The resource from “%1$S” was blocked due to MIME type mismatch (X-Content-Type-Options: nosniff).
 # LOCALIZATION NOTE: Do not translate "X-Content-Type-Options" and also do not trasnlate "nosniff".
 XCTOHeaderValueMissing=X-Content-Type-Options header warning: value was “%1$S”; did you mean to send “nosniff”?
 
 BlockScriptWithWrongMimeType=Script from “%1$S” was blocked because of a disallowed MIME type.
+
+# LOCALIZATION NOTE: Do not translate "data: URI".
+BlockTopLevelDataURINavigation=Navigation to toplevel data: URI not allowed (Blocked loading of: “%1$S”)
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1952,22 +1952,25 @@ private:
 class MediaDecoderStateMachine::CompletedState
   : public MediaDecoderStateMachine::StateObject
 {
 public:
   explicit CompletedState(Master* aPtr) : StateObject(aPtr) { }
 
   void Enter()
   {
+    // TODO : use more approriate way to decide whether need to release
+    // resource in bug1367983.
+#ifndef MOZ_WIDGET_ANDROID
     if (!mMaster->mLooping) {
       // We've decoded all samples.
       // We don't need decoders anymore if not looping.
       Reader()->ReleaseResources();
     }
-
+#endif
     bool hasNextFrame = (!mMaster->HasAudio() || !mMaster->mAudioCompleted)
                         && (!mMaster->HasVideo() || !mMaster->mVideoCompleted);
 
     mMaster->UpdateNextFrameStatus(
       hasNextFrame ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
                    : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
 
     Step();
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -273,16 +273,17 @@ GMPChild::RecvPreloadLibs(const nsCStrin
   // Pre-load DLLs that need to be used by the EME plugin but that can't be
   // loaded after the sandbox has started
   // Items in this must be lowercase!
   static const char *const whitelist[] = {
     "dxva2.dll", // Get monitor information
     "evr.dll", // MFGetStrideForBitmapInfoHeader
     "mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer, MFCreateMediaType
     "msmpeg2vdec.dll", // H.264 decoder
+    "psapi.dll", // For GetMappedFileNameW, see bug 1383611
   };
 
   nsTArray<nsCString> libs;
   SplitAt(", ", aLibs, libs);
   for (nsCString lib : libs) {
     ToLowerCase(lib);
     for (const char* whiteListedLib : whitelist) {
       if (lib.EqualsASCII(whiteListedLib)) {
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -786,17 +786,19 @@ GMPParent::ParseChromiumManifest(const n
   if (mDisplayName.EqualsASCII("clearkey")) {
     kEMEKeySystem = kEMEKeySystemClearkey;
 #if XP_WIN
     mLibs = NS_LITERAL_CSTRING("dxva2.dll, msmpeg2vdec.dll, evr.dll, mfh264dec.dll, mfplat.dll");
 #endif
   } else if (mDisplayName.EqualsASCII("WidevineCdm")) {
     kEMEKeySystem = kEMEKeySystemWidevine;
 #if XP_WIN
-    mLibs = NS_LITERAL_CSTRING("dxva2.dll");
+    // psapi.dll added for GetMappedFileNameW, which could possibly be avoided
+    // in future versions, see bug 1383611 for details.
+    mLibs = NS_LITERAL_CSTRING("dxva2.dll, psapi.dll");
 #endif
   } else {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   GMPCapability video;
 
   nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -487,16 +487,22 @@ AudioListener*
 AudioContext::Listener()
 {
   if (!mListener) {
     mListener = new AudioListener(this);
   }
   return mListener;
 }
 
+bool
+AudioContext::IsRunning() const
+{
+  return mAudioContextState == AudioContextState::Running;
+}
+
 already_AddRefed<Promise>
 AudioContext::DecodeAudioData(const ArrayBuffer& aBuffer,
                               const Optional<OwningNonNull<DecodeSuccessCallback> >& aSuccessCallback,
                               const Optional<OwningNonNull<DecodeErrorCallback> >& aFailureCallback,
                               ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   RefPtr<Promise> promise;
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -176,16 +176,17 @@ public:
 
   bool ShouldSuspendNewStream() const { return mSuspendCalled; }
 
   double CurrentTime() const;
 
   AudioListener* Listener();
 
   AudioContextState State() const { return mAudioContextState; }
+  bool IsRunning() const;
 
   // Those three methods return a promise to content, that is resolved when an
   // (possibly long) operation is completed on the MSG (and possibly other)
   // thread(s). To avoid having to match the calls and asychronous result when
   // the operation is completed, we keep a reference to the promises on the main
   // thread, and then send the promises pointers down the MSG thread, as a void*
   // (to make it very clear that the pointer is to merely be treated as an ID).
   // When back on the main thread, we can resolve or reject the promise, by
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -2334,57 +2334,67 @@ PluginInstanceChild::SetupFlashMsgThrott
     }
     else {
         // Already setup through quirks and the subclass.
         return;
     }
 }
 
 WNDPROC
-PluginInstanceChild::FlashThrottleAsyncMsg::GetProc()
+PluginInstanceChild::FlashThrottleMsg::GetProc()
 {
     if (mInstance) {
         return mWindowed ? mInstance->mPluginWndProc :
                            mInstance->mWinlessThrottleOldWndProc;
     }
     return nullptr;
 }
 
 NS_IMETHODIMP
-PluginInstanceChild::FlashThrottleAsyncMsg::Run()
+PluginInstanceChild::FlashThrottleMsg::Run()
 {
-    RemoveFromAsyncList();
+    if (!mInstance) {
+        return NS_OK;
+    }
+
+    mInstance->mPendingFlashThrottleMsgs.RemoveElement(this);
 
     // GetProc() checks mInstance, and pulls the procedure from
     // PluginInstanceChild. We don't transport sub-class procedure
-    // ptrs around in FlashThrottleAsyncMsg msgs.
+    // ptrs around in FlashThrottleMsg msgs.
     if (!GetProc())
         return NS_OK;
 
     // deliver the event to flash
     CallWindowProc(GetProc(), GetWnd(), GetMsg(), GetWParam(), GetLParam());
     return NS_OK;
 }
 
+nsresult
+PluginInstanceChild::FlashThrottleMsg::Cancel()
+{
+    MOZ_ASSERT(mInstance);
+    mInstance = nullptr;
+    return NS_OK;
+}
+
 void
 PluginInstanceChild::FlashThrottleMessage(HWND aWnd,
                                           UINT aMsg,
                                           WPARAM aWParam,
                                           LPARAM aLParam,
                                           bool isWindowed)
 {
-    // We reuse ChildAsyncCall so we get the cancelation work
-    // that's done in Destroy.
-    RefPtr<FlashThrottleAsyncMsg> task =
-        new FlashThrottleAsyncMsg(this, aWnd, aMsg, aWParam,
-                                  aLParam, isWindowed);
-    {
-        MutexAutoLock lock(mAsyncCallMutex);
-        mPendingAsyncCalls.AppendElement(task);
-    }
+    // We save a reference to the FlashThrottleMsg so we can cancel it in
+    // Destroy if it's still alive.
+    RefPtr<FlashThrottleMsg> task =
+        new FlashThrottleMsg(this, aWnd, aMsg, aWParam, aLParam, isWindowed);
+
+    mPendingFlashThrottleMsgs.AppendElement(task);
+
     MessageLoop::current()->PostDelayedTask(task.forget(),
                                             kFlashWMUSERMessageThrottleDelayMs);
 }
 
 #endif // OS_WIN
 
 mozilla::ipc::IPCResult
 PluginInstanceChild::AnswerSetPluginFocus()
@@ -4257,16 +4267,21 @@ PluginInstanceChild::Destroy()
     // PluginInstanceDestroyed call above.
     mCachedWindowActor = nullptr;
     mCachedElementActor = nullptr;
 
 #if defined(OS_WIN)
     DestroyWinlessPopupSurrogate();
     UnhookWinlessFlashThrottle();
     DestroyPluginWindow();
+
+    for (uint32_t i = 0; i < mPendingFlashThrottleMsgs.Length(); ++i) {
+        mPendingFlashThrottleMsgs[i]->Cancel();
+    }
+    mPendingFlashThrottleMsgs.Clear();
 #endif
 
     // Pending async calls are discarded, not delivered. This matches the
     // in-process behavior.
     for (uint32_t i = 0; i < mPendingAsyncCalls.Length(); ++i)
         mPendingAsyncCalls[i]->Cancel();
 
     mPendingAsyncCalls.Clear();
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -343,41 +343,41 @@ private:
     static BOOL WINAPI ImmReleaseContextProc(HWND aWND, HIMC aIMC);
     static LONG WINAPI ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex,
                                                    LPVOID aBuf, DWORD aLen);
     static BOOL WINAPI ImmSetCandidateWindowProc(HIMC hIMC,
                                                  LPCANDIDATEFORM plCandidate);
     static BOOL WINAPI ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex,
                                     DWORD aValue);
 
-    class FlashThrottleAsyncMsg : public ChildAsyncCall
+    class FlashThrottleMsg : public CancelableRunnable
     {
       public:
-        FlashThrottleAsyncMsg();
-        FlashThrottleAsyncMsg(PluginInstanceChild* aInst,
-                              HWND aWnd, UINT aMsg,
-                              WPARAM aWParam, LPARAM aLParam,
-                              bool isWindowed)
-          : ChildAsyncCall(aInst, nullptr, nullptr),
+        FlashThrottleMsg(PluginInstanceChild* aInstance, HWND aWnd, UINT aMsg,
+                         WPARAM aWParam, LPARAM aLParam, bool isWindowed)
+          : CancelableRunnable("FlashThrottleMsg"),
+          mInstance(aInstance),
           mWnd(aWnd),
           mMsg(aMsg),
           mWParam(aWParam),
           mLParam(aLParam),
           mWindowed(isWindowed)
         {}
 
         NS_IMETHOD Run() override;
+        nsresult Cancel() override;
 
         WNDPROC GetProc();
         HWND GetWnd() { return mWnd; }
         UINT GetMsg() { return mMsg; }
         WPARAM GetWParam() { return mWParam; }
         LPARAM GetLParam() { return mLParam; }
 
       private:
+        PluginInstanceChild* mInstance;
         HWND                 mWnd;
         UINT                 mMsg;
         WPARAM               mWParam;
         LPARAM               mLParam;
         bool                 mWindowed;
     };
 
     bool ShouldPostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam);
@@ -445,16 +445,19 @@ private:
     HWND mWinlessPopupSurrogateHWND;
     nsIntPoint mPluginSize;
     WNDPROC mWinlessThrottleOldWndProc;
     HWND mWinlessHiddenMsgHWND;
 #endif
 
     friend class ChildAsyncCall;
 
+#if defined(OS_WIN)
+    nsTArray<FlashThrottleMsg*> mPendingFlashThrottleMsgs;
+#endif
     Mutex mAsyncCallMutex;
     nsTArray<ChildAsyncCall*> mPendingAsyncCalls;
     nsTArray<nsAutoPtr<ChildTimer> > mTimers;
 
     /**
      * During destruction we enumerate all remaining scriptable objects and
      * invalidate/delete them. Enumeration can re-enter, so maintain a
      * hash separate from PluginModuleChild.mObjectMap.
--- a/dom/plugins/test/mochitest/browser.ini
+++ b/dom/plugins/test/mochitest/browser.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 support-files =
   head.js
+  plugin_data_url_test.html
   plugin_test.html
   plugin_subframe_test.html
   plugin_no_scroll_div.html
 
 [browser_bug1163570.js]
 skip-if = true # Bug 1249878
 [browser_bug1196539.js]
 skip-if = (!e10s || os != "win")
 [browser_tabswitchbetweenplugins.js]
 skip-if = (!e10s || os != "win")
 [browser_pluginscroll.js]
 skip-if = (true || !e10s || os != "win") # Bug 1213631
 [browser_bug1335475.js]
+[browser_data_url_plugin.js]
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/browser_data_url_plugin.js
@@ -0,0 +1,20 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [["security.data_uri.unique_opaque_origin", true]],
+  });
+  is(navigator.plugins.length, 0,
+     "plugins should not be available to chrome-privilege pages");
+
+  await BrowserTestUtils.withNewTab({ gBrowser, url: gTestRoot + "plugin_data_url_test.html" }, async function(browser) {
+    await ContentTask.spawn(browser, null, async function() {
+      ok(content.window.navigator.plugins.length > 0,
+         "plugins should be available to HTTP-loaded pages");
+      let dataFrameWin = content.document.getElementById("dataFrame").contentWindow;
+      is(dataFrameWin.navigator.plugins.length, 0,
+         "plugins should not be available to data: URI in iframe on a site");
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/plugin_data_url_test.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+  <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window"
+         style="position:absolute; top:50px; left:50px; width:500px; height:250px">
+<div style="display:block; height:3000px;"></div>
+
+<iframe id="dataFrame" src="data:text/html,foo" width="300" height="300"></iframe>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/browser.ini
@@ -0,0 +1,2 @@
+[DEFAULT]
+[browser_test_toplevel_data_navigations.js]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/browser_test_toplevel_data_navigations.js
@@ -0,0 +1,16 @@
+"use strict";
+
+const kDataBody = "toplevel navigation to data: URI allowed";
+const kDataURI = "data:text/html,<body>" + kDataBody + "</body>";
+
+add_task(async function test_nav_data_uri_click() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]],
+  });
+  await BrowserTestUtils.withNewTab(kDataURI, async function(browser) {
+    await ContentTask.spawn(gBrowser.selectedBrowser, {kDataBody}, async function({kDataBody}) { // eslint-disable-line
+     is(content.document.body.innerHTML, kDataBody,
+        "data: URI navigation from system should be allowed");
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/file_block_toplevel_data_navigation.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Toplevel data navigation</title>
+</head>
+<body>
+test1: clicking data: URI tries to navigate window<br/>
+<a id="testlink" href="data:text/html,<body>toplevel data: URI navigations should be blocked</body>">click me</a>
+<script>
+  document.getElementById('testlink').click();
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/file_block_toplevel_data_navigation2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Toplevel data navigation</title>
+</head>
+<body>
+test2: data: URI in iframe tries to window.open(data:, _blank);<br/>
+<iframe id="testFrame" src=""></iframe>
+<script>
+  let DATA_URI = `data:text/html,<body><script>
+    var win = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>", "_blank");
+    setTimeout(function () {
+      var result = win.document.body.innerHTML === "" ? "blocked" : "navigated";
+      parent.postMessage(result, "*");
+      win.close();
+    }, 1000);
+    <\/script></body>`;
+
+  window.addEventListener("message", receiveMessage);
+  function receiveMessage(event) {
+    window.removeEventListener("message", receiveMessage);
+    // propagate the information back to the caller
+    window.opener.postMessage(event.data, "*");
+  }
+  document.getElementById('testFrame').src = DATA_URI;
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/file_block_toplevel_data_navigation3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Toplevel data navigation</title>
+</head>
+<body>
+test3: performing data: URI navigation through win.loc.href<br/>
+<script>
+  window.location.href = "data:text/html,<body>toplevel data: URI navigations should be blocked</body>";
+</script>
+</body>
+</html>
--- a/dom/security/test/general/mochitest.ini
+++ b/dom/security/test/general/mochitest.ini
@@ -1,9 +1,13 @@
 [DEFAULT]
 support-files =
   file_contentpolicytype_targeted_link_iframe.sjs
   file_nosniff_testserver.sjs
   file_block_script_wrong_mime_server.sjs
+  file_block_toplevel_data_navigation.html
+  file_block_toplevel_data_navigation2.html
+  file_block_toplevel_data_navigation3.html
 
 [test_contentpolicytype_targeted_link_iframe.html]
 [test_nosniff.html]
 [test_block_script_wrong_mime.html]
+[test_block_toplevel_data_navigation.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/general/test_block_toplevel_data_navigation.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1331351 - Block top level window data: URI navigations</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true);
+SimpleTest.registerCleanupFunction(() => {
+  SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations");
+});
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("have to test that top level data: URI navgiation is blocked");
+
+function test1() {
+  // simple data: URI click navigation should be prevented
+  let TEST_FILE = "file_block_toplevel_data_navigation.html";
+  let win1 = window.open(TEST_FILE);
+  var readyStateCheckInterval = setInterval(function() {
+    let state = win1.document.readyState;
+    if (state === "interactive" || state === "complete") {
+      clearInterval(readyStateCheckInterval);
+      ok(win1.document.body.innerHTML.indexOf("test1:") !== -1,
+         "toplevel data: URI navigation through click() should be blocked");
+      win1.close();
+      test2();
+    }
+  }, 200);
+}
+
+function test2() {
+  // data: URI in iframe which opens data: URI in _blank should be blocked 
+  let win2 = window.open("file_block_toplevel_data_navigation2.html");
+  window.addEventListener("message", receiveMessage);
+  function receiveMessage(event) {
+    window.removeEventListener("message", receiveMessage);
+    is(event.data, "blocked",
+      "data: URI navigation using _blank from data: URI should be blocked");
+    win2.close();
+    test3();
+  }
+}
+
+function test3() {
+  // navigating to a data: URI using window.location.href should be blocked
+  let win3 = window.open("file_block_toplevel_data_navigation3.html");
+  setTimeout(function () {
+    ok(win3.document.body.innerHTML.indexOf("test3:") !== -1,
+      "data: URI navigation through win.loc.href should be blocked");
+    win3.close();
+    test4();
+  }, 1000);
+}
+
+function test4() {
+  // navigating to a data: URI using window.open() should be blocked
+  let win4 = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>");
+  setTimeout(function () {
+    // Please note that the data: URI will be displayed in the URL-Bar but not
+    // loaded, hence we rather rely on document.body than document.location
+    is(win4.document.body.innerHTML, "",
+      "navigating to a data: URI using window.open() should be blocked");
+    win4.close();
+    SimpleTest.finish();
+  }, 1000);
+}
+
+// fire up the tests
+test1();
+
+</script>
+</body>
+</html>
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -24,10 +24,11 @@ MOCHITEST_MANIFESTS += [
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'general/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'csp/browser.ini',
+    'general/browser.ini',
     'hsts/browser.ini',
 ]
--- a/dom/smil/crashtests/590425-1.html
+++ b/dom/smil/crashtests/590425-1.html
@@ -13,12 +13,12 @@ function boom()
   document.documentElement.removeAttribute("class");
 }
 
 </script>
 </head>
 
 <body onload="boom()">
 
-<iframe id="frame" src="data:text/html,%3Cbody%3E%3Csvg id=s%3E"></iframe>
+<iframe id="frame" srcdoc="<body><svg id=s>"></iframe>
 
 </body>
 </html>
--- a/dom/xhr/tests/browser_blobFromFile.js
+++ b/dom/xhr/tests/browser_blobFromFile.js
@@ -1,45 +1,60 @@
 let { classes: Cc, interfaces: Ci } = Components;
 
 add_task(async function test() {
   await SpecialPowers.pushPrefEnv(
     {set: [["browser.tabs.remote.separateFileUriProcess", true]]}
   );
 
+  let fileData = "";
+  for (var i = 0; i < 100; ++i) {
+    fileData += "hello world!";
+  }
+
   let file = Cc["@mozilla.org/file/directory_service;1"]
                .getService(Ci.nsIDirectoryService)
                .QueryInterface(Ci.nsIProperties)
                .get("ProfD", Ci.nsIFile);
+  file.append('file.txt');
+  file.createUnique(Components.interfaces.nsIFile.FILE_TYPE, 0o600);
+
+  let outStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                      .createInstance(Components.interfaces.nsIFileOutputStream);
+  outStream.init(file, 0x02 | 0x08 | 0x20, // write, create, truncate
+                 0666, 0);
+  outStream.write(fileData, fileData.length);
+  outStream.close();
 
   let fileHandler = Cc["@mozilla.org/network/io-service;1"]
                       .getService(Ci.nsIIOService)
                       .getProtocolHandler("file")
                       .QueryInterface(Ci.nsIFileProtocolHandler);
 
   let fileURL = fileHandler.getURLSpecFromFile(file);
 
   info("Opening url: " + fileURL);
   let tab = BrowserTestUtils.addTab(gBrowser, fileURL);
 
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
 
-  let blob = await ContentTask.spawn(browser, null, function() {
+  let blob = await ContentTask.spawn(browser, file.leafName, function(fileName) {
     return new content.window.Promise(resolve => {
       let xhr = new content.window.XMLHttpRequest();
       xhr.responseType = "blob";
-      xhr.open("GET", "prefs.js");
+      xhr.open("GET", fileName);
       xhr.send();
       xhr.onload = function() {
         resolve(xhr.response);
       }
     });
   });
 
   ok(blob instanceof File, "We have a file");
 
-  file.append("prefs.js");
   is(blob.size, file.fileSize, "The size matches");
-  is(blob.name, "prefs.js", "The name is correct");
+  is(blob.name, file.leafName, "The name is correct");
+
+  file.remove(false);
 
   gBrowser.removeTab(tab);
 });
--- a/editor/libeditor/crashtests/336081-1.xhtml
+++ b/editor/libeditor/crashtests/336081-1.xhtml
@@ -34,19 +34,19 @@ function init()
 }
 
 ]]>
 </script>
 </head>
 
 <body onload="init()">
 
-<iframe src="data:text/html," style="width: 95%; height: 500px;"/>
+<iframe srcdoc="<html></html>" style="width: 95%; height: 500px;"/>
 
 <div id="rootish">
 <div id="out1"/>
 <div id="in1"/>
 <div id="in2"/>
 <div id="out2"/>
 </div>
 
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/editor/libeditor/crashtests/382527-1.html
+++ b/editor/libeditor/crashtests/382527-1.html
@@ -2,17 +2,17 @@
 <html class="reftest-wait">
 <head>
 <script>
 
 
 function init1()
 {
   targetIframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
-  targetIframe.src = "data:text/html,";
+  targetIframe.srcdoc = "<html></html>";
   targetIframe.setAttribute("style", "width: 300px; height: 200px; border: 1px dotted green;");
   targetIframe.addEventListener("load", init2);
   document.body.appendChild(targetIframe);
 }
 
 
 function init2()
 {
--- a/editor/libeditor/crashtests/382778-1.html
+++ b/editor/libeditor/crashtests/382778-1.html
@@ -2,17 +2,17 @@
 <html class="reftest-wait">
 <head>
 <script>
 
 function init1()
 {
   // Create an html:iframe in HTML mode (so designMode can be used 320092)
   targetIframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
-  targetIframe.src = "data:text/html,";
+  targetIframe.srcdoc = "<html></html>";
   targetIframe.setAttribute("style", "width: 700px; height: 500px; border: 1px dotted green;");
   targetIframe.addEventListener("load", init2);
   document.body.appendChild(targetIframe);
 }
 
 
 function init2()
 {
--- a/gfx/2d/ScaledFontDWrite.cpp
+++ b/gfx/2d/ScaledFontDWrite.cpp
@@ -151,17 +151,29 @@ SkTypeface*
 ScaledFontDWrite::GetSkTypeface()
 {
   if (!mTypeface) {
     RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory();
     if (!factory) {
       return nullptr;
     }
 
-    mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, mForceGDIMode, mGamma, mContrast);
+    Float gamma = mGamma;
+    // Skia doesn't support a gamma value outside of 0-4, so default to 2.2
+    if (gamma < 0.0f || gamma > 4.0f) {
+      gamma = 2.2f;
+    }
+
+    Float contrast = mContrast;
+    // Skia doesn't support a contrast value outside of 0-1, so default to 1.0
+    if (contrast < 0.0f || contrast > 1.0f) {
+      contrast = 1.0f;
+    }
+
+    mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, mForceGDIMode, gamma, contrast);
   }
   return mTypeface;
 }
 #endif
 
 void
 ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint)
 {
--- a/gfx/tests/crashtests/1343666.html
+++ b/gfx/tests/crashtests/1343666.html
@@ -4,17 +4,17 @@
 <script>
 
 function f()
 {
     finish();
 }
 
 window.onload = function() {
-  let a = window.open("data:text/plain,test", null, "width=300,height=300");
+  let a = window.open("empty.html", null, "width=300,height=300");
   setTimeout(function(){
     a.close();
     a.addEventListener("vrdisplayconnect", function(){});
     window.close();
     document.documentElement.removeAttribute("class");
   }, 0);
 };
 
--- a/gfx/tests/crashtests/372094-1.xhtml
+++ b/gfx/tests/crashtests/372094-1.xhtml
@@ -28,18 +28,18 @@ function boom()
 }
 
 ]]>
 </script>
 </head>
 
 <body onload="init()">
 
-<iframe src="data:text/html," style="width: 95%; height: 500px;"/>
+  <iframe srcdoc="<html></html>" style="width: 95%; height: 500px;"/>
 
 <div id="rootish">
   <div>Foo</div>
   <div id="bar">Bar</div>
   <div><select><option id="baz">baz</option></select></div>
 </div>
 
 </body>
-</html>
\ No newline at end of file
+</html>
new file mode 100644
--- /dev/null
+++ b/gfx/tests/crashtests/empty.html
@@ -0,0 +1,1 @@
+test
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -31,59 +31,62 @@ gfxAlphaBoxBlur::~gfxAlphaBoxBlur()
 }
 
 already_AddRefed<gfxContext>
 gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx,
                       const gfxRect& aRect,
                       const IntSize& aSpreadRadius,
                       const IntSize& aBlurRadius,
                       const gfxRect* aDirtyRect,
-                      const gfxRect* aSkipRect)
+                      const gfxRect* aSkipRect,
+                      bool aUseHardwareAccel)
 {
   DrawTarget* refDT = aDestinationCtx->GetDrawTarget();
   Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing();
   Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing();
   RefPtr<DrawTarget> dt =
     InitDrawTarget(refDT, ToRect(aRect), aSpreadRadius, aBlurRadius,
-                   dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr));
+                   dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr),
+                   aUseHardwareAccel);
   if (!dt) {
     return nullptr;
   }
 
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
   MOZ_ASSERT(context); // already checked for target above
   context->SetMatrix(gfxMatrix::Translation(-mBlur.GetRect().TopLeft()));
   return context.forget();
 }
 
 already_AddRefed<DrawTarget>
 gfxAlphaBoxBlur::InitDrawTarget(const DrawTarget* aReferenceDT,
                                 const Rect& aRect,
                                 const IntSize& aSpreadRadius,
                                 const IntSize& aBlurRadius,
                                 const Rect* aDirtyRect,
-                                const Rect* aSkipRect)
+                                const Rect* aSkipRect,
+                                bool aUseHardwareAccel)
 {
   mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect);
   size_t blurDataSize = mBlur.GetSurfaceAllocationSize();
   if (blurDataSize == 0) {
     return nullptr;
   }
 
   BackendType backend = aReferenceDT->GetBackendType();
 
   // Check if the backend has an accelerated DrawSurfaceWithShadow.
   // Currently, only D2D1.1 supports this.
   // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread.
   // When blurring small draw targets such as short spans text, the cost of
   // creating and flushing an accelerated draw target may exceed the speedup
-  // gained from the faster blur, so we also make sure the blurred data exceeds
-  // a sufficient number of pixels to offset this cost.
+  // gained from the faster blur. It's up to the users of this blur
+  // to determine whether they want to use hardware acceleration.
   if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() &&
-      blurDataSize >= 8192 &&
+      aUseHardwareAccel &&
       backend == BackendType::DIRECT2D1_1) {
     mAccelerated = true;
     mDrawTarget =
       aReferenceDT->CreateShadowDrawTarget(mBlur.GetSize(),
                                            SurfaceFormat::A8,
                                            AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width));
   } else {
     // Make an alpha-only surface to draw on. We will play with the data after
--- a/gfx/thebes/gfxBlur.h
+++ b/gfx/thebes/gfxBlur.h
@@ -69,32 +69,37 @@ public:
      *
      * @param aDirtyRect A pointer to a dirty rect, measured in device units,
      *  if available. This will be used for optimizing the blur operation. It
      *  is safe to pass nullptr here.
      *
      * @param aSkipRect A pointer to a rect, measured in device units, that
      *  represents an area where blurring is unnecessary and shouldn't be done
      *  for speed reasons. It is safe to pass nullptr here.
+     *
+     * @param aUseHardwareAccel Flag to state whether or not we can use hardware
+     *  acceleration to speed up this blur.
      */
     already_AddRefed<gfxContext>
     Init(gfxContext* aDestinationCtx,
          const gfxRect& aRect,
          const mozilla::gfx::IntSize& aSpreadRadius,
          const mozilla::gfx::IntSize& aBlurRadius,
          const gfxRect* aDirtyRect,
-         const gfxRect* aSkipRect);
+         const gfxRect* aSkipRect,
+         bool aUseHardwareAccel = true);
 
     already_AddRefed<DrawTarget>
     InitDrawTarget(const mozilla::gfx::DrawTarget* aReferenceDT,
                    const mozilla::gfx::Rect& aRect,
                    const mozilla::gfx::IntSize& aSpreadRadius,
                    const mozilla::gfx::IntSize& aBlurRadius,
                    const mozilla::gfx::Rect* aDirtyRect = nullptr,
-                   const mozilla::gfx::Rect* aSkipRect = nullptr);
+                   const mozilla::gfx::Rect* aSkipRect = nullptr,
+                   bool aUseHardwareAccel = true);
 
     /**
      * Performs the blur and optionally colors the result if aShadowColor is not null.
      */
     already_AddRefed<mozilla::gfx::SourceSurface>
     DoBlur(const mozilla::gfx::Color* aShadowColor = nullptr,
            mozilla::gfx::IntPoint* aOutTopLeft = nullptr);
 
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1188,24 +1188,16 @@ gfxWindowsPlatform::SetupClearTypeParams
                 RegCloseKey(hKey);
             }
 
             if (contrast < 0.0 || contrast > 10.0) {
                 contrast = 1.0;
             }
         }
 
-        if (GetDefaultContentBackend() == BackendType::SKIA) {
-          // Skia doesn't support a contrast value outside of 0-1, so default to 1.0
-          if (contrast < 0.0 || contrast > 1.0) {
-            NS_WARNING("Custom dwrite contrast not supported in Skia. Defaulting to 1.0.");
-            contrast = 1.0;
-          }
-        }
-
         // For parameters that have not been explicitly set,
         // we copy values from default params (or our overridden value for contrast)
         if (gamma < 1.0 || gamma > 2.2) {
             gamma = defaultRenderingParams->GetGamma();
         }
 
         if (level < 0.0 || level > 1.0) {
             level = defaultRenderingParams->GetClearTypeLevel();
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -18,16 +18,17 @@ namespace JS {
     _(GetProp_Constant)                                 \
     _(GetProp_NotDefined)                               \
     _(GetProp_StaticName)                               \
     _(GetProp_SimdGetter)                               \
     _(GetProp_TypedObject)                              \
     _(GetProp_DefiniteSlot)                             \
     _(GetProp_Unboxed)                                  \
     _(GetProp_CommonGetter)                             \
+    _(GetProp_Static)                                   \
     _(GetProp_InlineAccess)                             \
     _(GetProp_Innerize)                                 \
     _(GetProp_InlineCache)                              \
     _(GetProp_SharedCache)                              \
     _(GetProp_ModuleNamespace)                          \
                                                         \
     _(SetProp_CommonSetter)                             \
     _(SetProp_TypedObject)                              \
--- a/js/src/devtools/automation/autospider.py
+++ b/js/src/devtools/automation/autospider.py
@@ -137,18 +137,21 @@ def ensure_dir_exists(name, clobber=True
 with open(os.path.join(DIR.scripts, "variants", args.variant)) as fh:
     variant = json.load(fh)
 
 if args.variant == 'nonunified':
     # Rewrite js/src/**/moz.build to replace UNIFIED_SOURCES to SOURCES.
     # Note that this modifies the current checkout.
     for dirpath, dirnames, filenames in os.walk(DIR.js_src):
         if 'moz.build' in filenames:
-            subprocess.check_call(['sed', '-i', 's/UNIFIED_SOURCES/SOURCES/',
-                                   os.path.join(dirpath, 'moz.build')])
+            in_place = ['-i']
+            if platform.system() == 'Darwin':
+                in_place.append('')
+            subprocess.check_call(['sed'] + in_place + ['s/UNIFIED_SOURCES/SOURCES/',
+                                                        os.path.join(dirpath, 'moz.build')])
 
 OBJDIR = os.path.join(DIR.source, args.objdir)
 OUTDIR = os.path.join(OBJDIR, "out")
 POBJDIR = posixpath.join(PDIR.source, args.objdir)
 AUTOMATION = env.get('AUTOMATION', False)
 MAKE = env.get('MAKE', 'make')
 MAKEFLAGS = env.get('MAKEFLAGS', '-j6' + ('' if AUTOMATION else ' -s'))
 UNAME_M = subprocess.check_output(['uname', '-m']).strip()
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -9511,16 +9511,25 @@ BytecodeEmitter::emitCallOrNew(ParseNode
         if (!emitGetName(pn2, callop))
             return false;
         break;
       case PNK_DOT:
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         if (pn2->as<PropertyAccess>().isSuper()) {
             if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop))
                 return false;
+        } else if ((pn->getOp() == JSOP_FUNCALL || pn->getOp() == JSOP_FUNAPPLY) &&
+                   pn2->expr()->getKind() == PNK_FUNCTION &&
+                   checkRunOnceContext()) {
+            // Top level lambdas whose .call or .apply methods are immediately
+            // invoked should be treated as run once lambdas.
+            emittingRunOnceLambda = true;
+            if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
+                return false;
+            emittingRunOnceLambda = false;
         } else {
             if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
                 return false;
         }
 
         break;
       case PNK_ELEM:
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -696,20 +696,21 @@ js::Nursery::doCollection(JS::gcreason::
 
     // Move objects pointed to by roots from the nursery to the major heap.
     TenuringTracer mover(rt, this);
 
     // Mark the store buffer. This must happen first.
     StoreBuffer& sb = runtime()->gc.storeBuffer();
 
     // The MIR graph only contains nursery pointers if cancelIonCompilations()
-    // is set on the store buffer, in which case we cancel all compilations.
+    // is set on the store buffer, in which case we cancel all compilations
+    // of such graphs.
     startProfile(ProfileKey::CancelIonCompilations);
     if (sb.cancelIonCompilations())
-        js::CancelOffThreadIonCompile(rt);
+        js::CancelOffThreadIonCompilesUsingNurseryPointers(rt);
     endProfile(ProfileKey::CancelIonCompilations);
 
     startProfile(ProfileKey::TraceValues);
     sb.traceValues(mover);
     endProfile(ProfileKey::TraceValues);
 
     startProfile(ProfileKey::TraceCells);
     sb.traceCells(mover);
--- a/js/src/gdb/mozilla/asmjs.py
+++ b/js/src/gdb/mozilla/asmjs.py
@@ -14,25 +14,25 @@ def on_stop(event):
     if isinstance(event, gdb.SignalEvent) and event.stop_signal == 'SIGSEGV':
         # Allocate memory for sigaction, once per js shell process.
         process = gdb.selected_inferior()
         buf = sigaction_buffers.get(process)
         if buf is None:
             buf = gdb.parse_and_eval("(struct sigaction *) malloc(sizeof(struct sigaction))")
             sigaction_buffers[process] = buf
 
-        # See if AsmJSFaultHandler is installed as the SIGSEGV signal action.
+        # See if WasmFaultHandler is installed as the SIGSEGV signal action.
         sigaction_fn = gdb.parse_and_eval('__sigaction')
         sigaction_fn(SIGSEGV, 0, buf)
-        AsmJSFaultHandler = gdb.parse_and_eval("AsmJSFaultHandler")
-        if buf['__sigaction_handler']['sa_handler'] == AsmJSFaultHandler:
+        WasmFaultHandler = gdb.parse_and_eval("WasmFaultHandler<(Signal)0>")
+        if buf['__sigaction_handler']['sa_handler'] == WasmFaultHandler:
             # Advise the user that magic is happening.
-            print("js/src/gdb/mozilla/asmjs.py: Allowing AsmJSFaultHandler to run.")
+            print("js/src/gdb/mozilla/asmjs.py: Allowing WasmFaultHandler to run.")
 
-            # If AsmJSFaultHandler doesn't handle this segfault, it will unhook
+            # If WasmFaultHandler doesn't handle this segfault, it will unhook
             # itself and re-raise.
             gdb.execute("continue")
 
 def on_exited(event):
     if event.inferior in sigaction_buffers:
         del sigaction_buffers[event.inferior]
 
 def install():
--- a/js/src/gdb/mozilla/unwind.py
+++ b/js/src/gdb/mozilla/unwind.py
@@ -38,16 +38,17 @@ def debug(something):
 SizeOfFramePrefix = {
     'JitFrame_IonJS': 'ExitFrameLayout',
     'JitFrame_BaselineJS': 'JitFrameLayout',
     'JitFrame_BaselineStub': 'BaselineStubFrameLayout',
     'JitFrame_IonStub': 'JitStubFrameLayout',
     'JitFrame_Entry': 'JitFrameLayout',
     'JitFrame_Rectifier': 'RectifierFrameLayout',
     'JitFrame_IonAccessorIC': 'IonAccessorICFrameLayout',
+    'JitFrame_IonICCall': 'IonICCallFrameLayout',
     'JitFrame_Exit': 'ExitFrameLayout',
     'JitFrame_Bailout': 'JitFrameLayout',
 }
 
 # All types and symbols that we need are attached to an object that we
 # can dispose of as needed.
 class UnwinderTypeCache(object):
     def __init__(self):
@@ -324,22 +325,22 @@ class UnwinderState(object):
         return False
 
     # See whether |pc| is claimed by the Jit.
     def is_jit_address(self, pc):
         if self.proc_mappings != None:
             return not self.text_address_claimed(pc)
 
         cx = self.get_tls_context()
-        runtime = cx['runtime_']
-        if runtime == 0:
+        runtime = cx['runtime_']['value']
+        if long(runtime.address) == 0:
             return False
 
         jitRuntime = runtime['jitRuntime_']
-        if jitRuntime == 0:
+        if long(jitRuntime.address) == 0:
             return False
 
         execAllocators = [jitRuntime['execAlloc_'], jitRuntime['backedgeExecAlloc_']]
         for execAlloc in execAllocators:
             for pool in jsjitExecutableAllocator(execAlloc, self.typecache):
                 pages = pool['m_allocation']['pages']
                 size = pool['m_allocation']['size']
                 if pages <= pc and pc < pages + size:
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bailoutStaticObject.js
@@ -0,0 +1,28 @@
+// Test bailouts from loading particular non-singleton static objects.
+
+function wrap(fun) {
+    function wrapper() {
+	return fun.apply(this, arguments);
+    }
+    return wrapper;
+}
+
+var adder = wrap(function(a, b) { return a + b; });
+var subber = wrap(function(a, b) { return a - b; });
+var tmp = adder;
+adder = subber;
+adder = tmp;
+
+function foo() {
+    var i = 0;
+    var a = 0;
+    for (var i = 0; i < 10000; i++) {
+	a = adder(a, 1);
+	a = subber(a, 1);
+    }
+    return a;
+}
+
+assertEq(foo(), 0);
+adder = subber;
+assertEq(foo(), -20000);
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1998,16 +1998,17 @@ jit::FinishBailoutToBaseline(BaselineBai
       case Bailout_NonObjectInput:
       case Bailout_NonStringInput:
       case Bailout_NonSymbolInput:
       case Bailout_UnexpectedSimdInput:
       case Bailout_NonSharedTypedArrayInput:
       case Bailout_Debugger:
       case Bailout_UninitializedThis:
       case Bailout_BadDerivedConstructorReturn:
+      case Bailout_LoadStaticObject:
         // Do nothing.
         break;
 
       case Bailout_FirstExecution:
         // Do not return directly, as this was not frequent in the first place,
         // thus rely on the check for frequent bailouts to recompile the current
         // script.
         break;
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -1035,17 +1035,17 @@ GetMegamorphicGetterSetterFunction(ICStu
     JSObject* obj = isGetter ? propShape->getterObject() : propShape->setterObject();
     return &obj->as<JSFunction>();
 }
 
 bool
 BaselineInspector::megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter,
                                                    JSFunction** getterOrSetter)
 {
-    if (!hasBaselineScript())
+    if (!hasBaselineScript() || *pc == JSOP_SETALIASEDVAR)
         return false;
 
     *getterOrSetter = nullptr;
     const ICEntry& entry = icEntryFromPC(pc);
 
     for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
         if (stub->isCacheIR_Monitored()) {
             MOZ_ASSERT(isGetter);
@@ -1186,17 +1186,17 @@ AddCacheIRSetPropFunction(ICCacheIR_Upda
 }
 
 bool
 BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
                                          JSFunction** commonSetter, bool* isOwnProperty,
                                          ReceiverVector& receivers,
                                          ObjectGroupVector& convertUnboxedGroups)
 {
-    if (!hasBaselineScript())
+    if (!hasBaselineScript() || *pc == JSOP_SETALIASEDVAR)
         return false;
 
     MOZ_ASSERT(receivers.empty());
     MOZ_ASSERT(convertUnboxedGroups.empty());
 
     *commonSetter = nullptr;
     const ICEntry& entry = icEntryFromPC(pc);
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7270,27 +7270,16 @@ IonBuilder::ensureDefiniteTypeSet(MDefin
     }
 
     // Create a NOP mir instruction to filter the typeset.
     MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), def, types);
     current->add(filter);
     return filter;
 }
 
-static size_t
-NumFixedSlots(JSObject* object)
-{
-    // Note: we can't use object->numFixedSlots() here, as this will read the
-    // shape and can race with the active thread if we are building off thread.
-    // The allocation kind and object class (which goes through the type) can
-    // be read freely, however.
-    gc::AllocKind kind = object->asTenured().getAllocKind();
-    return gc::GetGCKindSlots(kind, object->getClass());
-}
-
 static bool
 IsUninitializedGlobalLexicalSlot(JSObject* obj, PropertyName* name)
 {
     LexicalEnvironmentObject &globalLexical = obj->as<LexicalEnvironmentObject>();
     MOZ_ASSERT(globalLexical.isGlobal());
     Shape* shape = globalLexical.lookupPure(name);
     if (!shape)
         return false;
@@ -7302,42 +7291,58 @@ IonBuilder::getStaticName(bool* emitted,
                           MDefinition* lexicalCheck)
 {
     MOZ_ASSERT(*emitted == false);
 
     jsid id = NameToId(name);
 
     bool isGlobalLexical = staticObject->is<LexicalEnvironmentObject>() &&
                            staticObject->as<LexicalEnvironmentObject>().isGlobal();
-    MOZ_ASSERT(isGlobalLexical ||
-               staticObject->is<GlobalObject>() ||
-               staticObject->is<CallObject>() ||
-               staticObject->is<ModuleEnvironmentObject>());
-    MOZ_ASSERT(staticObject->isSingleton());
 
     // Always emit the lexical check. This could be optimized, but is
     // currently not for simplicity's sake.
     if (lexicalCheck)
         return Ok();
 
+    // Only optimize accesses on native objects.
+    if (!staticObject->isNative())
+        return Ok();
+
+    // Only optimize accesses on own data properties.
+    Shape* propertyShape = staticObject->as<NativeObject>().lastProperty()->searchLinear(NameToId(name));
+    if (!propertyShape || !propertyShape->isDataDescriptor() || !propertyShape->hasSlot())
+        return Ok();
+    uint32_t slot = propertyShape->slot();
+
     TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(staticObject);
     if (analysisContext)
         staticKey->ensureTrackedProperty(analysisContext, NameToId(name));
 
-    if (staticKey->unknownProperties())
-        return Ok();
-
-    HeapTypeSetKey property = staticKey->property(id);
-    if (!property.maybeTypes() ||
-        !property.maybeTypes()->definiteProperty() ||
-        property.nonData(constraints()))
-    {
-        // The property has been reconfigured as non-configurable, non-enumerable
-        // or non-writable.
-        return Ok();
+    // Make sure the property is a normal data property. This is not done for
+    // call objects, as they are not tracked by TI and their data properties
+    // cannot be dynamically reconfigured.
+    Maybe<HeapTypeSetKey> property;
+    if (!staticObject->is<CallObject>()) {
+        if (staticKey->unknownProperties())
+            return Ok();
+
+        property.emplace(staticKey->property(id));
+
+        if (property.ref().nonData(constraints()) ||
+            !property.ref().maybeTypes() ||
+            !property.ref().maybeTypes()->definiteProperty())
+        {
+            // We can't be sure the slot will match at runtime, so include a
+            // shape guard on the object.
+            MInstruction* obj = MConstant::NewConstraintlessObject(alloc(), staticObject);
+            current->add(obj);
+            addShapeGuard(obj, staticObject->as<NativeObject>().lastProperty(), Bailout_ShapeGuard);
+        } else {
+            MOZ_ASSERT(slot == property.ref().maybeTypes()->definiteSlot());
+        }
     }
 
     // Don't optimize global lexical bindings if they aren't initialized at
     // compile time.
     if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name))
         return Ok();
 
     *emitted = true;
@@ -7353,23 +7358,47 @@ IonBuilder::getStaticName(bool* emitted,
             if (testSingletonProperty(staticObject, id) == singleton) {
                 pushConstant(ObjectValue(*singleton));
                 return Ok();
             }
         }
 
         // Try to inline properties that have never been overwritten.
         Value constantValue;
-        if (property.constant(constraints(), &constantValue)) {
+        if (property.isSome() && property.ref().constant(constraints(), &constantValue)) {
             pushConstant(constantValue);
             return Ok();
         }
     }
 
-    MOZ_TRY(loadStaticSlot(staticObject, barrier, types, property.maybeTypes()->definiteSlot()));
+    MOZ_TRY(loadStaticSlot(staticObject, barrier, types, slot));
+
+    // If the static object has a function object stored in this property,
+    // test that the result is that specific function. This is yet another
+    // technique for trying to force a property load to be a specific value,
+    // and is included because other mechanisms (property types and observed
+    // types) do not always work, especially in polymorphic framework code.
+    // We restrict this optimization to function properties, as they are less
+    // likely to change over time and are more likely to require precise
+    // information for inlining decisions.
+    if (!outermostBuilder()->script()->hadFrequentBailouts()) {
+        Value v = staticObject->as<NativeObject>().getSlot(slot);
+        if (v.isObject() &&
+            v.toObject().is<JSFunction>() &&
+            v.toObject().as<JSFunction>().isInterpreted())
+        {
+            JSObject* result = checkNurseryObject(&v.toObject().as<JSFunction>());
+            MDefinition* load = current->pop();
+            MInstruction* expected = MConstant::NewConstraintlessObject(alloc(), result);
+            expected->setResultTypeSet(MakeSingletonTypeSet(constraints(), result));
+            current->add(expected);
+            current->add(MGuardObjectIdentity::New(alloc(), load, expected, false, Bailout_LoadStaticObject));
+            current->push(expected);
+        }
+    }
 
     return Ok();
 }
 
 AbortReasonOr<Ok>
 IonBuilder::loadStaticSlot(JSObject* staticObject, BarrierKind barrier, TemporaryTypeSet* types,
                            uint32_t slot)
 {
@@ -7387,17 +7416,17 @@ IonBuilder::loadStaticSlot(JSObject* sta
     }
 
     MInstruction* obj = constant(ObjectValue(*staticObject));
 
     MIRType rvalType = types->getKnownMIRType();
     if (barrier != BarrierKind::NoBarrier)
         rvalType = MIRType::Value;
 
-    return loadSlot(obj, slot, NumFixedSlots(staticObject), rvalType, barrier, types);
+    return loadSlot(obj, slot, staticObject->as<NativeObject>().numFixedSlots(), rvalType, barrier, types);
 }
 
 // Whether a write of the given value may need a post-write barrier for GC purposes.
 bool
 jit::NeedsPostBarrier(MDefinition* value)
 {
     if (!GetJitContext()->compartment->zone()->nurseryExists())
         return false;
@@ -7452,17 +7481,18 @@ IonBuilder::setStaticName(JSObject* stat
     // If the property has a known type, we may be able to optimize typed stores by not
     // storing the type tag.
     MIRType slotType = MIRType::None;
     MIRType knownType = property.knownMIRType(constraints());
     if (knownType != MIRType::Value)
         slotType = knownType;
 
     bool needsPreBarrier = property.needsBarrier(constraints());
-    return storeSlot(obj, property.maybeTypes()->definiteSlot(), NumFixedSlots(staticObject),
+    return storeSlot(obj, property.maybeTypes()->definiteSlot(),
+                     staticObject->as<NativeObject>().numFixedSlots(),
                      value, needsPreBarrier, slotType);
 }
 
 JSObject*
 IonBuilder::testGlobalLexicalBinding(PropertyName* name)
 {
     MOZ_ASSERT(JSOp(*pc) == JSOP_BINDGNAME ||
                JSOp(*pc) == JSOP_GETGNAME ||
@@ -8534,18 +8564,17 @@ IonBuilder::getElemAddCache(MDefinition*
 }
 
 TemporaryTypeSet*
 IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes, const jsid id)
 {
     if (objTypes->unknownObject() || objTypes->getObjectCount() == 0)
         return nullptr;
 
-    TemporaryTypeSet empty;
-    TemporaryTypeSet* acc = &empty;
+    TemporaryTypeSet* acc = nullptr;
     LifoAlloc* lifoAlloc = alloc().lifoAlloc();
 
     Vector<HeapTypeSetKey, 4, SystemAllocPolicy> properties;
     if (!properties.reserve(objTypes->getObjectCount()))
         return nullptr;
 
     for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
         TypeSet::ObjectKey* key = objTypes->getObject(i);
@@ -8555,17 +8584,24 @@ IonBuilder::computeHeapType(const Tempor
 
         HeapTypeSetKey property = key->property(id);
         HeapTypeSet* currentSet = property.maybeTypes();
 
         if (!currentSet || currentSet->unknown())
             return nullptr;
 
         properties.infallibleAppend(property);
-        acc = TypeSet::unionSets(acc, currentSet, lifoAlloc);
+
+        if (acc) {
+            acc = TypeSet::unionSets(acc, currentSet, lifoAlloc);
+        } else {
+            TemporaryTypeSet empty;
+            acc = TypeSet::unionSets(&empty, currentSet, lifoAlloc);
+        }
+
         if (!acc)
             return nullptr;
     }
 
     // Freeze all the properties associated with the refined type set.
     for (HeapTypeSetKey* i = properties.begin(); i != properties.end(); i++)
         i->freeze(constraints());
 
@@ -10387,16 +10423,22 @@ IonBuilder::jsop_getprop(PropertyName* n
             return Ok();
 
         // Try to inline a common property getter, or make a call.
         trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
         MOZ_TRY(getPropTryCommonGetter(&emitted, obj, name, types));
         if (emitted)
             return Ok();
 
+        // Try to optimize for loads from a specific object.
+        trackOptimizationAttempt(TrackedStrategy::GetProp_Static);
+        MOZ_TRY(getPropTryStaticAccess(&emitted, obj, name, barrier, types));
+        if (emitted)
+            return Ok();
+
         // Try to emit a monomorphic/polymorphic access based on baseline caches.
         trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess);
         MOZ_TRY(getPropTryInlineAccess(&emitted, obj, name, barrier, types));
         if (emitted)
             return Ok();
 
         // Try to emit loads from a module namespace.
         trackOptimizationAttempt(TrackedStrategy::GetProp_ModuleNamespace);
@@ -11187,16 +11229,27 @@ PropertyShapesHaveSameSlot(const Baselin
             return nullptr;
         }
     }
 
     return firstShape;
 }
 
 AbortReasonOr<Ok>
+IonBuilder::getPropTryStaticAccess(bool* emitted, MDefinition* obj, PropertyName* name,
+                                   BarrierKind barrier, TemporaryTypeSet* types)
+{
+    if (!obj->isConstant() || obj->type() != MIRType::Object)
+        return Ok();
+
+    obj->setImplicitlyUsedUnchecked();
+    return getStaticName(emitted, &obj->toConstant()->toObject(), name);
+}
+
+AbortReasonOr<Ok>
 IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                    BarrierKind barrier, TemporaryTypeSet* types)
 {
     MOZ_ASSERT(*emitted == false);
 
     BaselineInspector::ReceiverVector receivers(alloc());
     BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
     if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
@@ -12619,21 +12672,59 @@ IonBuilder::walkEnvironmentChain(unsigne
         MInstruction* ins = MEnclosingEnvironment::New(alloc(), env);
         current->add(ins);
         env = ins;
     }
 
     return env;
 }
 
+static bool
+SearchEnvironmentChainForCallObject(JSObject* environment, JSScript* script, JSObject** pcall)
+{
+    while (environment && !environment->is<GlobalObject>()) {
+        if (environment->is<CallObject>() &&
+            environment->as<CallObject>().callee().nonLazyScript() == script)
+        {
+            *pcall = environment;
+            return true;
+        }
+        environment = environment->enclosingEnvironment();
+    }
+    return false;
+}
+
 bool
 IonBuilder::hasStaticEnvironmentObject(EnvironmentCoordinate ec, JSObject** pcall)
 {
     JSScript* outerScript = EnvironmentCoordinateFunctionScript(script(), pc);
-    if (!outerScript || !outerScript->treatAsRunOnce())
+    if (!outerScript)
+        return false;
+
+    // JSOP_SETALIASEDVAR only emits a cache when the outer script is a run
+    // once script. To avoid problems with the generic jsop_setprop() paths,
+    // only use static environment objects when a baseline cache exists.
+    if (*pc == JSOP_SETALIASEDVAR && !outerScript->treatAsRunOnce())
+        return false;
+
+    // If the callee is a specific JSFunction then there is a specific
+    // environment object on its chain we can use.
+    if (inlineCallInfo_) {
+        MDefinition* calleeDef = inlineCallInfo_->fun();
+        if (calleeDef->isConstant()) {
+            JSFunction* callee = &calleeDef->toConstant()->toObject().template as<JSFunction>();
+            JSObject* environment = callee->environment();
+            if (SearchEnvironmentChainForCallObject(environment, outerScript, pcall))
+                return true;
+        }
+    }
+
+    // Otherwise, if the outer script will only run once then we can go looking
+    // for its call object.
+    if (!outerScript->treatAsRunOnce())
         return false;
 
     TypeSet::ObjectKey* funKey =
         TypeSet::ObjectKey::get(outerScript->functionNonDelazifying());
     if (funKey->hasFlags(constraints(), OBJECT_FLAG_RUNONCE_INVALIDATED))
         return false;
 
     // The script this aliased var operation is accessing will run only once,
@@ -12644,42 +12735,28 @@ IonBuilder::hasStaticEnvironmentObject(E
     // Look for the call object on the current script's function's env chain.
     // If the current script is inner to the outer script and the function has
     // singleton type then it should show up here.
 
     MDefinition* envDef = current->getSlot(info().environmentChainSlot());
     envDef->setImplicitlyUsedUnchecked();
 
     JSObject* environment = script()->functionNonDelazifying()->environment();
-    while (environment && !environment->is<GlobalObject>()) {
-        if (environment->is<CallObject>() &&
-            environment->as<CallObject>().callee().nonLazyScript() == outerScript)
-        {
-            MOZ_ASSERT(environment->isSingleton());
-            *pcall = environment;
-            return true;
-        }
-        environment = environment->enclosingEnvironment();
-    }
+    if (SearchEnvironmentChainForCallObject(environment, outerScript, pcall))
+        return true;
 
     // Look for the call object on the current frame, if we are compiling the
     // outer script itself. Don't do this if we are at entry to the outer
     // script, as the call object we see will not be the real one --- after
     // entering the Ion code a different call object will be created.
 
     if (script() == outerScript && baselineFrame_ && info().osrPc()) {
         JSObject* singletonScope = baselineFrame_->singletonEnvChain;
-        if (singletonScope &&
-            singletonScope->is<CallObject>() &&
-            singletonScope->as<CallObject>().callee().nonLazyScript() == outerScript)
-        {
-            MOZ_ASSERT(singletonScope->isSingleton());
-            *pcall = singletonScope;
+        if (SearchEnvironmentChainForCallObject(singletonScope, outerScript, pcall))
             return true;
-        }
     }
 
     return true;
 }
 
 MDefinition*
 IonBuilder::getAliasedVar(EnvironmentCoordinate ec)
 {
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -241,16 +241,18 @@ class IonBuilder
     AbortReasonOr<Ok> getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name,
                                              BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyName* name,
                                                 BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
                                         BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name,
                                              TemporaryTypeSet* types, bool innerized = false);
+    AbortReasonOr<Ok> getPropTryStaticAccess(bool* emitted, MDefinition* obj, PropertyName* name,
+                                             BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                              BarrierKind barrier, TemporaryTypeSet* types);
     AbortReasonOr<Ok> getPropTryTypedObject(bool* emitted, MDefinition* obj, PropertyName* name);
     AbortReasonOr<Ok> getPropTryScalarPropOfTypedObject(bool* emitted, MDefinition* typedObj,
                                                         int32_t fieldOffset,
                                                         TypedObjectPrediction fieldTypeReprs);
     AbortReasonOr<Ok> getPropTryReferencePropOfTypedObject(bool* emitted, MDefinition* typedObj,
                                                            int32_t fieldOffset,
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -137,16 +137,19 @@ enum BailoutKind
     Bailout_NonStringInputInvalidate,
 
     // Used for integer division, multiplication and modulo.
     // If there's a remainder, bails to return a double.
     // Can also signal overflow or result of -0.
     // Can also signal division by 0 (returns inf, a double).
     Bailout_DoubleOutput,
 
+    // Load of a value from a static object retrieved an unexpected value.
+    Bailout_LoadStaticObject,
+
     // END Invalid assumptions bailouts
 
 
     // A bailout at the very start of a function indicates that there may be
     // a type mismatch in the arguments that necessitates a reflow.
     Bailout_ArgumentCheck,
 
     // A bailout triggered by a bounds-check failure.
@@ -228,16 +231,18 @@ BailoutKindString(BailoutKind kind)
 
       // Bailouts caused by invalid assumptions.
       case Bailout_OverflowInvalidate:
         return "Bailout_OverflowInvalidate";
       case Bailout_NonStringInputInvalidate:
         return "Bailout_NonStringInputInvalidate";
       case Bailout_DoubleOutput:
         return "Bailout_DoubleOutput";
+      case Bailout_LoadStaticObject:
+        return "Bailout_LoadStaticObject";
 
       // Other bailouts.
       case Bailout_ArgumentCheck:
         return "Bailout_ArgumentCheck";
       case Bailout_BoundsCheck:
         return "Bailout_BoundsCheck";
       case Bailout_Detached:
         return "Bailout_Detached";
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3895,17 +3895,17 @@ LIRGenerator::visitCallBindVar(MCallBind
     define(lir, ins);
 }
 
 void
 LIRGenerator::visitGuardObjectIdentity(MGuardObjectIdentity* ins)
 {
     LGuardObjectIdentity* guard = new(alloc()) LGuardObjectIdentity(useRegister(ins->object()),
                                                                     useRegister(ins->expected()));
-    assignSnapshot(guard, Bailout_ObjectIdentityOrTypeGuard);
+    assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->object());
 }
 
 void
 LIRGenerator::visitGuardClass(MGuardClass* ins)
 {
     LDefinition t = temp();
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -6192,19 +6192,28 @@ jit::PropertyReadNeedsTypeBarrier(JSCont
             if (obj->unknownProperties())
                 break;
 
             HeapTypeSetKey property = obj->property(NameToId(name));
             if (property.maybeTypes()) {
                 TypeSet::TypeList types;
                 if (!property.maybeTypes()->enumerateTypes(&types))
                     break;
-                if (types.length() == 1) {
+                // If there is a single possible type for the property,
+                // optimistically add it to the observed set. Don't do this
+                // for the special uninitialized lexical type, which will
+                // never actually be observed here and will cause problems
+                // downstream during compilation.
+                if (types.length() == 1 &&
+                    (!types[0].isPrimitive() ||
+                     types[0].primitive() != JSVAL_TYPE_MAGIC))
+                {
                     // Note: the return value here is ignored.
                     observed->addType(types[0], GetJitContext()->temp->lifoAlloc());
+                    break;
                 }
                 break;
             }
 
             if (!obj->proto().isObject())
                 break;
             obj = TypeSet::ObjectKey::get(obj->proto().toObject());
         } while (obj);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -11485,39 +11485,47 @@ class MGuardObjectGroup
 };
 
 // Guard on an object's identity, inclusively or exclusively.
 class MGuardObjectIdentity
   : public MBinaryInstruction,
     public SingleObjectPolicy::Data
 {
     bool bailOnEquality_;
-
-    MGuardObjectIdentity(MDefinition* obj, MDefinition* expected, bool bailOnEquality)
+    BailoutKind bailoutKind_;
+
+    MGuardObjectIdentity(MDefinition* obj, MDefinition* expected, bool bailOnEquality,
+                         BailoutKind bailoutKind = Bailout_ObjectIdentityOrTypeGuard)
       : MBinaryInstruction(obj, expected),
-        bailOnEquality_(bailOnEquality)
+        bailOnEquality_(bailOnEquality),
+        bailoutKind_(bailoutKind)
     {
         setGuard();
         setMovable();
         setResultType(MIRType::Object);
     }
 
   public:
     INSTRUCTION_HEADER(GuardObjectIdentity)
     TRIVIAL_NEW_WRAPPERS
     NAMED_OPERANDS((0, object), (1, expected))
 
     bool bailOnEquality() const {
         return bailOnEquality_;
     }
+    BailoutKind bailoutKind() const {
+        return bailoutKind_;
+    }
     bool congruentTo(const MDefinition* ins) const override {
         if (!ins->isGuardObjectIdentity())
             return false;
         if (bailOnEquality() != ins->toGuardObjectIdentity()->bailOnEquality())
             return false;
+        if (bailoutKind() != ins->toGuardObjectIdentity()->bailoutKind())
+            return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const override {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Guard on an object's class.
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -143,54 +143,60 @@ GetSelectorRuntime(const CompilationSele
 {
     struct Matcher
     {
         JSRuntime* match(JSScript* script)    { return script->runtimeFromActiveCooperatingThread(); }
         JSRuntime* match(JSCompartment* comp) { return comp->runtimeFromActiveCooperatingThread(); }
         JSRuntime* match(ZonesInState zbs)    { return zbs.runtime; }
         JSRuntime* match(JSRuntime* runtime)  { return runtime; }
         JSRuntime* match(AllCompilations all) { return nullptr; }
+        JSRuntime* match(CompilationsUsingNursery cun) { return cun.runtime; }
     };
 
     return selector.match(Matcher());
 }
 
 static bool
 JitDataStructuresExist(const CompilationSelector& selector)
 {
     struct Matcher
     {
         bool match(JSScript* script)    { return !!script->compartment()->jitCompartment(); }
         bool match(JSCompartment* comp) { return !!comp->jitCompartment(); }
         bool match(ZonesInState zbs)    { return zbs.runtime->hasJitRuntime(); }
         bool match(JSRuntime* runtime)  { return runtime->hasJitRuntime(); }
         bool match(AllCompilations all) { return true; }
+        bool match(CompilationsUsingNursery cun) { return cun.runtime->hasJitRuntime(); }
     };
 
     return selector.match(Matcher());
 }
 
 static bool
-CompiledScriptMatches(const CompilationSelector& selector, JSScript* target)
+IonBuilderMatches(const CompilationSelector& selector, jit::IonBuilder* builder)
 {
-    struct ScriptMatches
+    struct BuilderMatches
     {
-        JSScript* target_;
+        jit::IonBuilder* builder_;
 
-        bool match(JSScript* script)    { return script == target_; }
-        bool match(JSCompartment* comp) { return comp == target_->compartment(); }
-        bool match(JSRuntime* runtime)  { return runtime == target_->runtimeFromAnyThread(); }
+        bool match(JSScript* script)    { return script == builder_->script(); }
+        bool match(JSCompartment* comp) { return comp == builder_->script()->compartment(); }
+        bool match(JSRuntime* runtime)  { return runtime == builder_->script()->runtimeFromAnyThread(); }
         bool match(AllCompilations all) { return true; }
         bool match(ZonesInState zbs)    {
-            return zbs.runtime == target_->runtimeFromAnyThread() &&
-                   zbs.state == target_->zoneFromAnyThread()->gcState();
+            return zbs.runtime == builder_->script()->runtimeFromAnyThread() &&
+                   zbs.state == builder_->script()->zoneFromAnyThread()->gcState();
+        }
+        bool match(CompilationsUsingNursery cun) {
+            return cun.runtime == builder_->script()->runtimeFromAnyThread() &&
+                   !builder_->safeForMinorGC();
         }
     };
 
-    return selector.match(ScriptMatches{target});
+    return selector.match(BuilderMatches{builder});
 }
 
 void
 js::CancelOffThreadIonCompile(const CompilationSelector& selector, bool discardLazyLinkList)
 {
     if (!JitDataStructuresExist(selector))
         return;
 
@@ -198,30 +204,30 @@ js::CancelOffThreadIonCompile(const Comp
 
     if (!HelperThreadState().threads)
         return;
 
     /* Cancel any pending entries for which processing hasn't started. */
     GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist(lock);
     for (size_t i = 0; i < worklist.length(); i++) {
         jit::IonBuilder* builder = worklist[i];
-        if (CompiledScriptMatches(selector, builder->script())) {
+        if (IonBuilderMatches(selector, builder)) {
             FinishOffThreadIonCompile(builder, lock);
             HelperThreadState().remove(worklist, &i);
         }
     }
 
     /* Wait for in progress entries to finish up. */
     bool cancelled;
     do {
         cancelled = false;
         bool unpaused = false;
         for (auto& helper : *HelperThreadState().threads) {
             if (helper.ionBuilder() &&
-                CompiledScriptMatches(selector, helper.ionBuilder()->script()))
+                IonBuilderMatches(selector, helper.ionBuilder()))
             {
                 helper.ionBuilder()->cancel();
                 if (helper.pause) {
                     helper.pause = false;
                     unpaused = true;
                 }
                 cancelled = true;
             }
@@ -231,32 +237,32 @@ js::CancelOffThreadIonCompile(const Comp
         if (cancelled)
             HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
     } while (cancelled);
 
     /* Cancel code generation for any completed entries. */
     GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
     for (size_t i = 0; i < finished.length(); i++) {
         jit::IonBuilder* builder = finished[i];
-        if (CompiledScriptMatches(selector, builder->script())) {
+        if (IonBuilderMatches(selector, builder)) {
             builder->script()->zone()->group()->numFinishedBuilders--;
             jit::FinishOffThreadBuilder(nullptr, builder, lock);
             HelperThreadState().remove(finished, &i);
         }
     }
 
     /* Cancel lazy linking for pending builders (attached to the ionScript). */
     if (discardLazyLinkList) {
         MOZ_ASSERT(!selector.is<AllCompilations>());
         JSRuntime* runtime = GetSelectorRuntime(selector);
         for (ZoneGroupsIter group(runtime); !group.done(); group.next()) {
             jit::IonBuilder* builder = group->ionLazyLinkList().getFirst();
             while (builder) {
                 jit::IonBuilder* next = builder->getNext();
-                if (CompiledScriptMatches(selector, builder->script()))
+                if (IonBuilderMatches(selector, builder))
                     jit::FinishOffThreadBuilder(runtime, builder, lock);
                 builder = next;
             }
         }
     }
 }
 
 #ifdef DEBUG
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -489,21 +489,23 @@ StartOffThreadIonCompile(JSContext* cx, 
 /*
  * Schedule deletion of Ion compilation data.
  */
 bool
 StartOffThreadIonFree(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock);
 
 struct AllCompilations {};
 struct ZonesInState { JSRuntime* runtime; JS::Zone::GCState state; };
+struct CompilationsUsingNursery { JSRuntime* runtime; };
 
 using CompilationSelector = mozilla::Variant<JSScript*,
                                              JSCompartment*,
                                              ZonesInState,
                                              JSRuntime*,
+                                             CompilationsUsingNursery,
                                              AllCompilations>;
 
 /*
  * Cancel scheduled or in progress Ion compilations.
  */
 void
 CancelOffThreadIonCompile(const CompilationSelector& selector, bool discardLazyLinkList);
 
@@ -527,16 +529,22 @@ CancelOffThreadIonCompile(JSRuntime* run
 
 inline void
 CancelOffThreadIonCompile(JSRuntime* runtime)
 {
     CancelOffThreadIonCompile(CompilationSelector(runtime), true);
 }
 
 inline void
+CancelOffThreadIonCompilesUsingNurseryPointers(JSRuntime* runtime)
+{
+    CancelOffThreadIonCompile(CompilationSelector(CompilationsUsingNursery{runtime}), true);
+}
+
+inline void
 CancelOffThreadIonCompile()
 {
     CancelOffThreadIonCompile(CompilationSelector(AllCompilations()), false);
 }
 
 #ifdef DEBUG
 bool
 HasOffThreadIonCompile(JSCompartment* comp);
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -393,17 +393,17 @@ InitGlobalLexicalOperation(JSContext* cx
                            JSScript* script, jsbytecode* pc, HandleValue value)
 {
     MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
                   lexicalEnvArg == &cx->global()->lexicalEnvironment());
     MOZ_ASSERT(*pc == JSOP_INITGLEXICAL);
     Rooted<LexicalEnvironmentObject*> lexicalEnv(cx, lexicalEnvArg);
     RootedShape shape(cx, lexicalEnv->lookup(cx, script->getName(pc)));
     MOZ_ASSERT(shape);
-    lexicalEnv->setSlot(shape->slot(), value);
+    lexicalEnv->setSlotWithType(cx, shape, value);
 }
 
 inline bool
 InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
 {
     if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
         unsigned propAttrs = GetInitDataPropAttrs(op);
         return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2106,17 +2106,17 @@ class ConstraintDataConstantProperty
     JSCompartment* maybeCompartment() { return nullptr; }
 };
 
 } /* anonymous namespace */
 
 bool
 HeapTypeSetKey::constant(CompilerConstraintList* constraints, Value* valOut)
 {
-    if (nonData(constraints))
+    if (nonData(constraints) || !object()->isSingleton())
         return false;
 
     // Only singleton object properties can be marked as constants.
     JSObject* obj = object()->singleton();
     if (!obj || !obj->isNative())
         return false;
 
     if (maybeTypes() && maybeTypes()->nonConstantProperty())
--- a/js/xpconnect/crashtests/732870.html
+++ b/js/xpconnect/crashtests/732870.html
@@ -13,11 +13,11 @@ function boom()
   frameDoc.close();
   document.documentElement.removeAttribute("class");
 }
 
 </script>
 </head>
 
 <body onload="boom();">
-<iframe id="f" src="data:text/html,1"></iframe>
+<iframe id="f" srcdoc="<html>1</html>"></iframe>
 </body>
 </html>
--- a/js/xpconnect/crashtests/751995.html
+++ b/js/xpconnect/crashtests/751995.html
@@ -27,10 +27,10 @@ function boom()
   frameDoc().write("2");
 
   // All done.
   document.documentElement.removeAttribute("class");
 }
 
 </script>
 </head>
-<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+<body onload="boom();"><iframe id="f" srcdoc="1"></iframe></body>
 </html>
--- a/js/xpconnect/crashtests/753162.html
+++ b/js/xpconnect/crashtests/753162.html
@@ -14,10 +14,10 @@ function boom()
   };
   frameDoc.addEventListener("DOMAttrModified", f);
   frameDoc.write("<html d>");
 }
 
 </script>
 </head>
 
-<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+<body onload="boom();"><iframe id="f" srcdoc="1"></iframe></body>
 </html>
--- a/js/xpconnect/crashtests/806751.html
+++ b/js/xpconnect/crashtests/806751.html
@@ -16,11 +16,11 @@ function boom()
 function finish() {
   document.documentElement.removeAttribute('class');
 }
 
 </script>
 </head>
 
 <body onload="boom();">
-<iframe id="frame" src="data:text/html,1"></iframe>
+<iframe id="frame" srcdoc="1"></iframe>
 </body>
 </html>
--- a/js/xpconnect/crashtests/851418.html
+++ b/js/xpconnect/crashtests/851418.html
@@ -13,11 +13,11 @@ function boom()
   frameRoot.onload;
   document.documentElement.removeAttribute("class");
 }
 
 </script>
 </head>
 
 <body onload="boom();">
-<iframe id="f" src="data:text/html,1"></iframe>
+<iframe id="f" srcdoc="1"></iframe>
 </body>
 </html>
--- a/js/xpconnect/crashtests/898939.html
+++ b/js/xpconnect/crashtests/898939.html
@@ -8,11 +8,11 @@ function boom()
 {
   document.documentElement.appendChild(document.getElementById("f").contentDocument.getElementById("m"));
   document.documentElement.removeAttribute("class");
 }
 
 </script>
 </head>
 <body onload="boom();">
-<iframe id="f" src="data:text/html;charset=UTF-8,<!DOCTYPE html><body><marquee id=m>"></iframe>
+<iframe id="f" srcdoc="<!DOCTYPE html><body><marquee id=m>"></iframe>
 </body>
 </html>
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -452,19 +452,19 @@ this.XPCOMUtils = {
   /**
    * Allows you to fake a relative import. Expects the global object from the
    * module that's calling us, and the relative filename that we wish to import.
    */
   importRelative: function XPCOMUtils__importRelative(that, path, scope) {
     if (!("__URI__" in that))
       throw Error("importRelative may only be used from a JSM, and its first argument "+
                   "must be that JSM's global object (hint: use this)");
-    let uri = that.__URI__;
-    let i = uri.lastIndexOf("/");
-    Components.utils.import(uri.substring(0, i+1) + path, scope || that);
+
+    Cu.importGlobalProperties(["URL"]);
+    Components.utils.import(new URL(path, that.__URI__).href, scope || that);
   },
 
   /**
    * generates a singleton nsIFactory implementation that can be used as
    * the _xpcom_factory of the component.
    * @param aServiceConstructor
    *        Constructor function of the component.
    */
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -191,16 +191,17 @@ ReportOnCallerUTF8(JSCLContextHelper& he
     va_end(ap);
     return NS_OK;
 }
 
 mozJSComponentLoader::mozJSComponentLoader()
     : mModules(16),
       mImports(16),
       mInProgressImports(16),
+      mLocations(16),
       mInitialized(false)
 {
     MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton");
 
     sSelf = this;
 }
 
 #define ENSURE_DEP(name) { nsresult rv = Ensure##name(); NS_ENSURE_SUCCESS(rv, rv); }
@@ -241,40 +242,37 @@ class MOZ_STACK_CLASS ComponentLoaderInf
                              nullptr, // aLoadGroup
                              nullptr, // aCallbacks
                              nsIRequest::LOAD_NORMAL,
                              mIOService);
     }
 
     nsIURI* ResolvedURI() { MOZ_ASSERT(mResolvedURI); return mResolvedURI; }
     nsresult EnsureResolvedURI() {
-        BEGIN_ENSURE(ResolvedURI, ScriptChannel);
-        return mScriptChannel->GetURI(getter_AddRefs(mResolvedURI));
+        BEGIN_ENSURE(ResolvedURI, URI);
+        return ResolveURI(mURI, getter_AddRefs(mResolvedURI));
     }
 
-    nsAutoCString& Key() { return *mKey; }
+    const nsACString& Key() { return mLocation; }
     nsresult EnsureKey() {
-        ENSURE_DEPS(ResolvedURI);
-        mKey.emplace();
-        return mResolvedURI->GetSpec(*mKey);
+        return NS_OK;
     }
 
     MOZ_MUST_USE nsresult GetLocation(nsCString& aLocation) {
         nsresult rv = EnsureURI();
         NS_ENSURE_SUCCESS(rv, rv);
         return mURI->GetSpec(aLocation);
     }
 
   private:
     const nsACString& mLocation;
     nsCOMPtr<nsIIOService> mIOService;
     nsCOMPtr<nsIURI> mURI;
     nsCOMPtr<nsIChannel> mScriptChannel;
     nsCOMPtr<nsIURI> mResolvedURI;
-    Maybe<nsAutoCString> mKey; // This is safe because we're MOZ_STACK_CLASS
 };
 
 #undef BEGIN_ENSURE
 #undef ENSURE_DEPS
 #undef ENSURE_DEP
 
 mozJSComponentLoader::~mozJSComponentLoader()
 {
@@ -444,16 +442,17 @@ SizeOfTableExcludingThis(const nsBaseHas
 }
 
 size_t
 mozJSComponentLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
 {
     size_t n = aMallocSizeOf(this);
     n += SizeOfTableExcludingThis(mModules, aMallocSizeOf);
     n += SizeOfTableExcludingThis(mImports, aMallocSizeOf);
+    n += mLocations.ShallowSizeOfExcludingThis(aMallocSizeOf);
     n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf);
     return n;
 }
 
 void
 mozJSComponentLoader::CreateLoaderGlobal(JSContext* aCx,
                                          nsACString& aLocation,
                                          JSAddonId* aAddonID,
@@ -608,18 +607,20 @@ mozJSComponentLoader::ObjectForLocation(
 
     // Before compiling the script, first check to see if we have it in
     // the startupcache.  Note: as a rule, startupcache errors are not fatal
     // to loading the script, since we can always slow-load.
 
     bool writeToCache = false;
     StartupCache* cache = StartupCache::GetSingleton();
 
+    aInfo.EnsureResolvedURI();
+
     nsAutoCString cachePath(kJSCachePrefix);
-    rv = PathifyURI(aInfo.URI(), cachePath);
+    rv = PathifyURI(aInfo.ResolvedURI(), cachePath);
     NS_ENSURE_SUCCESS(rv, rv);
 
     script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
     if (!script && cache) {
         ReadCachedScript(cache, cachePath, cx, &script);
     }
 
     if (script) {
@@ -756,16 +757,17 @@ mozJSComponentLoader::ObjectForLocation(
 
 void
 mozJSComponentLoader::UnloadModules()
 {
     mInitialized = false;
 
     mInProgressImports.Clear();
     mImports.Clear();
+    mLocations.Clear();
 
     for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) {
         iter.Data()->Clear();
         iter.Remove();
     }
 }
 
 nsresult
@@ -899,59 +901,66 @@ mozJSComponentLoader::ImportInto(const n
 
     nsresult rv;
     if (!mInitialized) {
         rv = ReallyInit();
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     ComponentLoaderInfo info(aLocation);
-    rv = info.EnsureResolvedURI();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // get the JAR if there is one
-    nsCOMPtr<nsIJARURI> jarURI;
-    jarURI = do_QueryInterface(info.ResolvedURI(), &rv);
-    nsCOMPtr<nsIFileURL> baseFileURL;
-    if (NS_SUCCEEDED(rv)) {
-        nsCOMPtr<nsIURI> baseURI;
-        while (jarURI) {
-            jarURI->GetJARFile(getter_AddRefs(baseURI));
-            jarURI = do_QueryInterface(baseURI, &rv);
-        }
-        baseFileURL = do_QueryInterface(baseURI, &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-        baseFileURL = do_QueryInterface(info.ResolvedURI(), &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    nsCOMPtr<nsIFile> sourceFile;
-    rv = baseFileURL->GetFile(getter_AddRefs(sourceFile));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIFile> sourceLocalFile;
-    sourceLocalFile = do_QueryInterface(sourceFile, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
 
     rv = info.EnsureKey();
     NS_ENSURE_SUCCESS(rv, rv);
 
     ModuleEntry* mod;
     nsAutoPtr<ModuleEntry> newEntry;
     if (!mImports.Get(info.Key(), &mod) && !mInProgressImports.Get(info.Key(), &mod)) {
         newEntry = new ModuleEntry(RootingContext::get(callercx));
         if (!newEntry)
             return NS_ERROR_OUT_OF_MEMORY;
+
+        rv = info.EnsureResolvedURI();
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // get the JAR if there is one
+        nsCOMPtr<nsIJARURI> jarURI;
+        jarURI = do_QueryInterface(info.ResolvedURI(), &rv);
+        nsCOMPtr<nsIFileURL> baseFileURL;
+        if (NS_SUCCEEDED(rv)) {
+            nsCOMPtr<nsIURI> baseURI;
+            while (jarURI) {
+                jarURI->GetJARFile(getter_AddRefs(baseURI));
+                jarURI = do_QueryInterface(baseURI, &rv);
+            }
+            baseFileURL = do_QueryInterface(baseURI, &rv);
+            NS_ENSURE_SUCCESS(rv, rv);
+        } else {
+            baseFileURL = do_QueryInterface(info.ResolvedURI(), &rv);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+
+        nsCOMPtr<nsIFile> sourceFile;
+        rv = baseFileURL->GetFile(getter_AddRefs(sourceFile));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = info.ResolvedURI()->GetSpec(newEntry->resolvedURL);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        nsCString* existingPath;
+        if (mLocations.Get(newEntry->resolvedURL, &existingPath) && *existingPath != info.Key()) {
+            return NS_ERROR_UNEXPECTED;
+        }
+
+        mLocations.Put(newEntry->resolvedURL, new nsCString(info.Key()));
         mInProgressImports.Put(info.Key(), newEntry);
 
         rv = info.EnsureURI();
         NS_ENSURE_SUCCESS(rv, rv);
         RootedValue exception(callercx);
-        rv = ObjectForLocation(info, sourceLocalFile, &newEntry->obj,
+        rv = ObjectForLocation(info, sourceFile, &newEntry->obj,
                                &newEntry->thisObjectKey,
                                &newEntry->location, true, &exception);
 
         mInProgressImports.Remove(info.Key());
 
         if (NS_FAILED(rv)) {
             if (!exception.isUndefined()) {
                 // An exception was thrown during compilation. Propagate it
@@ -1107,16 +1116,17 @@ mozJSComponentLoader::Unload(const nsACS
         return NS_OK;
     }
 
     ComponentLoaderInfo info(aLocation);
     rv = info.EnsureKey();
     NS_ENSURE_SUCCESS(rv, rv);
     ModuleEntry* mod;
     if (mImports.Get(info.Key(), &mod)) {
+        mLocations.Remove(mod->resolvedURL);
         mImports.Remove(info.Key());
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 mozJSComponentLoader::Observe(nsISupports* subject, const char* topic,
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -139,29 +139,35 @@ class mozJSComponentLoader : public mozi
         size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
         static already_AddRefed<nsIFactory> GetFactory(const mozilla::Module& module,
                                                        const mozilla::Module::CIDEntry& entry);
 
         nsCOMPtr<xpcIJSGetFactory> getfactoryobj;
         JS::PersistentRootedObject obj;
         JS::PersistentRootedScript thisObjectKey;
-        char*               location;
+        char* location;
+        nsCString resolvedURL;
     };
 
     friend class ModuleEntry;
 
     static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData,
                                                mozilla::MallocSizeOf aMallocSizeOf, void* arg);
     static size_t ClassEntrySizeOfExcludingThis(const nsACString& aKey,
                                                 const nsAutoPtr<ModuleEntry>& aData,
                                                 mozilla::MallocSizeOf aMallocSizeOf, void* arg);
 
     // Modules are intentionally leaked, but still cleared.
     nsDataHashtable<nsCStringHashKey, ModuleEntry*> mModules;
 
     nsClassHashtable<nsCStringHashKey, ModuleEntry> mImports;
     nsDataHashtable<nsCStringHashKey, ModuleEntry*> mInProgressImports;
 
+    // A map of on-disk file locations which are loaded as modules to the
+    // pre-resolved URIs they were loaded from. Used to prevent the same file
+    // from being loaded separately, from multiple URLs.
+    nsClassHashtable<nsCStringHashKey, nsCString> mLocations;
+
     bool mInitialized;
 };
 
 #endif
--- a/js/xpconnect/tests/components/js/xpctest_attributes.js
+++ b/js/xpconnect/tests/components/js/xpctest_attributes.js
@@ -1,12 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-Components.utils.import("resource:///modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function TestObjectReadWrite() {}
 TestObjectReadWrite.prototype = {
 
   /* Boilerplate */
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces["nsIXPCTestObjectReadWrite"]]),
   contractID: "@mozilla.org/js/xpc/test/js/ObjectReadWrite;1",
   classID: Components.ID("{8ff41d9c-66e9-4453-924a-7d8de0a5e966}"),
--- a/js/xpconnect/tests/components/js/xpctest_bug809674.js
+++ b/js/xpconnect/tests/components/js/xpctest_bug809674.js
@@ -1,12 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-Components.utils.import("resource:///modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function TestBug809674() {}
 TestBug809674.prototype = {
 
   /* Boilerplate */
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces["nsIXPCTestBug809674"]]),
   contractID: "@mozilla.org/js/xpc/test/js/Bug809674;1",
   classID: Components.ID("{2df46559-da21-49bf-b863-0d7b7bbcbc73}"),
--- a/js/xpconnect/tests/components/js/xpctest_interfaces.js
+++ b/js/xpconnect/tests/components/js/xpctest_interfaces.js
@@ -1,12 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-Components.utils.import("resource:///modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function TestInterfaceA() {}
 TestInterfaceA.prototype = {
 
   /* Boilerplate */
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces["nsIXPCTestInterfaceA"]]),
   contractID: "@mozilla.org/js/xpc/test/js/TestInterfaceA;1",
   classID: Components.ID("{3c8fd2f5-970c-42c6-b5dd-cda1c16dcfd8}"),
--- a/js/xpconnect/tests/components/js/xpctest_params.js
+++ b/js/xpconnect/tests/components/js/xpctest_params.js
@@ -1,12 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-Components.utils.import("resource:///modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function TestParams() {
 }
 
 /* For once I'm happy that JS is weakly typed. */
 function f(a, b) {
     var rv = b.value;
     b.value = a;
--- a/js/xpconnect/tests/components/js/xpctest_returncode_child.js
+++ b/js/xpconnect/tests/components/js/xpctest_returncode_child.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 const {interfaces: Ci, classes: Cc, utils: Cu, results: Cr} = Components;
-Cu.import("resource:///modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function TestReturnCodeChild() {}
 TestReturnCodeChild.prototype = {
 
   /* Boilerplate */
   QueryInterface: XPCOMUtils.generateQI([Ci["nsIXPCTestReturnCodeChild"]]),
   contractID: "@mozilla.org/js/xpc/test/js/ReturnCodeChild;1",
   classID: Components.ID("{38dd78aa-467f-4fad-8dcf-4383a743e235}"),
--- a/js/xpconnect/tests/components/js/xpctest_utils.js
+++ b/js/xpconnect/tests/components/js/xpctest_utils.js
@@ -1,13 +1,13 @@
 /* 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/. */
 
-Components.utils.import("resource:///modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function TestUtils() {}
 TestUtils.prototype = {
 
   /* Boilerplate */
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces["nsIXPCTestUtils"]]),
   contractID: "@mozilla.org/js/xpc/test/js/TestUtils;1",
   classID: Components.ID("{e86573c4-a384-441a-8c92-7b99e8575b28}"),
--- a/js/xpconnect/tests/unit/test_import.js
+++ b/js/xpconnect/tests/unit/test_import.js
@@ -34,21 +34,18 @@ function run_test() {
 
   // try on a new object using the resolved URL
   var res = Components.classes["@mozilla.org/network/protocol;1?name=resource"]
                       .getService(Components.interfaces.nsIResProtocolHandler);
   var resURI = res.newURI("resource://gre/modules/XPCOMUtils.jsm");
   dump("resURI: " + resURI + "\n");
   var filePath = res.resolveURI(resURI);
   var scope3 = {};
-  Components.utils.import(filePath, scope3);
-  do_check_eq(typeof(scope3.XPCOMUtils), "object");
-  do_check_eq(typeof(scope3.XPCOMUtils.generateNSGetFactory), "function");
-  
-  do_check_true(scope3.XPCOMUtils == scope.XPCOMUtils);
+  Assert.throws(() => Components.utils.import(filePath, scope3),
+                /NS_ERROR_UNEXPECTED/);
 
   // make sure we throw when the second arg is bogus
   var didThrow = false;
   try {
       Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", "wrong");
   } catch (ex) {
       print("exception (expected): " + ex);
       didThrow = true;
--- a/js/xpconnect/tests/unit/test_isModuleLoaded.js
+++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js
@@ -16,18 +16,9 @@ function run_test() {
                 "isModuleLoaded returned correct value for non-loaded module");
   try {
     Cu.import("resource://gre/modules/ISO8601DateUtils1.jsm");
     do_check_true(false,
                   "Should have thrown while trying to load a non existing file");
   } catch (ex) {}
   do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
-
-  // incorrect url
-  try {
-    Cu.isModuleLoaded("resource://modules/ISO8601DateUtils1.jsm");
-    do_check_true(false,
-                  "Should have thrown while trying to load a non existing file");
-  } catch (ex) {
-    do_check_true(true, "isModuleLoaded threw an exception while loading incorrect uri");
-  }
 }
--- a/js/xpconnect/tests/unit/test_returncode.js
+++ b/js/xpconnect/tests/unit/test_returncode.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {interfaces: Ci, classes: Cc, utils: Cu, manager: Cm, results: Cr} = Components;
 
-Cu.import("resource:///modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function getConsoleMessages() {
   let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
   let messages = consoleService.getMessageArray().map((m) => m.toString());
   // reset ready for the next call.
   consoleService.reset();
   return messages;
 }
--- a/layout/base/crashtests/606432-1.html
+++ b/layout/base/crashtests/606432-1.html
@@ -15,10 +15,10 @@ function boom()
 function finish()
 {
   document.documentElement.className = "";
 }
 
 </script>
 </head>
 
-<body onload="setTimeout(boom, 200);"><iframe id="f" src="data:application/xhtml+xml,<html xmlns='http://www.w3.org/1999/xhtml'></html>"></iframe></body>
+<body onload="setTimeout(boom, 200);"><iframe id="f" srcdoc="<html xmlns='http://www.w3.org/1999/xhtml'></html>"></iframe></body>
 </html>
--- a/layout/base/crashtests/645572-1.html
+++ b/layout/base/crashtests/645572-1.html
@@ -13,17 +13,17 @@ function start(){
         tmp.id='ifr42257';
         o230.ownerDocument.documentElement.appendChild(tmp); 
         start_dataiframe11();
         //window.setTimeout('start_dataiframe11()',100);
 }function start_dataiframe11(){
         o232=o230.ownerDocument.getElementById('ifr42257').contentDocument.documentElement;
         o234=o196;
         tmp=o234.ownerDocument.createElement('iframe');
-        tmp.src='data:text/html,' + escape("<q id='element2'><q id='element3'><q id='element4'><dd style id='element6'>");
+        tmp.srcdoc="<q id='element2'><q id='element3'><q id='element4'><dd style id='element6'>";
         tmp.id='ifr22371';
         tmp.addEventListener("load", start_dataiframe12);
         o234.ownerDocument.documentElement.appendChild(tmp);
 }function start_dataiframe12(){
         o239=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element2');
         o240=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element3');
         o241=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element4');
         o243=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element6');
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -6020,17 +6020,18 @@ nsLayoutUtils::PaintTextShadow(const nsI
 
     nsRect shadowRect(aTextRect);
     shadowRect.MoveBy(shadowOffset);
 
     nsPresContext* presCtx = aFrame->PresContext();
     nsContextBoxBlur contextBoxBlur;
     gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
                                                     presCtx->AppUnitsPerDevPixel(),
-                                                    aDestCtx, aDirtyRect, nullptr);
+                                                    aDestCtx, aDirtyRect, nullptr,
+                                                    nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR);
     if (!shadowContext)
       continue;
 
     nscolor shadowColor;
     if (shadowDetails->mHasColor)
       shadowColor = shadowDetails->mColor;
     else
       shadowColor = aForegroundColor;
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -321,17 +321,17 @@ support-files =
 [test_remote_frame.html]
 [test_resize_flush.html]
 support-files = resize_flush_iframe.html
 [test_scroll_event_ordering.html]
 [test_scroll_selection_into_view.html]
 skip-if = toolkit == 'android' # Bug 1355844
 support-files = scroll_selection_into_view_window.html
 [test_scroll_snapping.html]
-skip-if = toolkit == 'android' # Bug 1355851
+skip-if = toolkit == 'android' || os == 'win' # Bug 1355851, win Bug 1379810
 [test_scroll_snapping_scrollbars.html]
 skip-if = toolkit == 'android' # Bug 1355851
 [test_transformed_scrolling_repaints.html]
 [test_transformed_scrolling_repaints_2.html]
 [test_transformed_scrolling_repaints_3.html]
 support-files = transformed_scrolling_repaints_3_window.html
 
 # *** Please maintain alphabetical ordering when adding new tests ***
--- a/layout/base/tests/test_bug399284.html
+++ b/layout/base/tests/test_bug399284.html
@@ -97,17 +97,17 @@ function encodeUTF16LE(string)
         encodedString += encodeURI(string.charAt(i));
         encodedString += "%00";
     }
     return encodedString;
 }
 
 function testFontSize(frame)
 {
-    var iframeDoc = $(frame).contentDocument;
+    var iframeDoc = SpecialPowers.wrap($(frame)).contentDocument;
     var size = parseInt(iframeDoc.defaultView.
                 getComputedStyle(iframeDoc.getElementById("testPara"), 
                                  null).
                 getPropertyValue("font-size"));
     ok(size > 0, "font size assigned for " + frame);
 }
 </script>
 </pre>
--- a/layout/generic/crashtests/324318-1.html
+++ b/layout/generic/crashtests/324318-1.html
@@ -19,11 +19,11 @@ function init() {
 }
 
 window.addEventListener("load", init);
 document.documentElement.setAttribute("class", "reftest-wait");
 
 </script>
 
 <frameset resizable="yes" rows="50%,*">
- <frame name="one" src="data:text/html,<table border='1'><tr><td>tdc</td></tr></table>">
- <frame name="two" src="data:text/html,">
+ <frame name="one" src="file_324318-1.html">
+ <frame name="two" src="empty.html">
 </frameset>
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/empty.html
@@ -0,0 +1,1 @@
+<html></html>
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/file_324318-1.html
@@ -0,0 +1,1 @@
+<table border='1'><tr><td>tdc</td></tr></table>
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -6967,22 +6967,24 @@ nsTextFrame::PaintShadows(nsCSSShadowArr
   // Add bounds of text decorations
   gfxRect decorationRect(0, -shadowMetrics.mAscent,
       shadowMetrics.mAdvanceWidth, shadowMetrics.mAscent + shadowMetrics.mDescent);
   shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
                                        decorationRect);
 
   // If the textrun uses any color or SVG fonts, we need to force use of a mask
   // for shadow rendering even if blur radius is zero.
-  uint32_t blurFlags = 0;
+  // Force disable hardware acceleration for text shadows since it's usually
+  // more expensive than just doing it on the CPU.
+  uint32_t blurFlags = nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR;
   uint32_t numGlyphRuns;
   const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
   while (numGlyphRuns-- > 0) {
     if (run->mFont->AlwaysNeedsMaskForShadow()) {
-      blurFlags = nsContextBoxBlur::FORCE_MASK;
+      blurFlags |= nsContextBoxBlur::FORCE_MASK;
       break;
     }
     run++;
   }
 
   if (mTextRun->IsVertical()) {
     Swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
     Swap(shadowMetrics.mBoundingBox.width, shadowMetrics.mBoundingBox.height);
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -4282,23 +4282,26 @@ nsContextBoxBlur::Init(const nsRect& aRe
 
   gfxMatrix transform = aDestinationCtx->CurrentMatrix();
   rect = transform.TransformBounds(rect);
 
   mPreTransformed = !transform.IsIdentity();
 
   // Create the temporary surface for blurring
   dirtyRect = transform.TransformBounds(dirtyRect);
+  bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
   if (aSkipRect) {
     gfxRect skipRect = transform.TransformBounds(*aSkipRect);
     mContext = mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius,
-                                  blurRadius, &dirtyRect, &skipRect);
+                                  blurRadius, &dirtyRect, &skipRect,
+                                  useHardwareAccel);
   } else {
     mContext = mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius,
-                                  blurRadius, &dirtyRect, nullptr);
+                                  blurRadius, &dirtyRect, nullptr,
+                                  useHardwareAccel);
   }
 
   if (mContext) {
     // we don't need to blur if skipRect is equal to rect
     // and mContext will be nullptr
     mContext->Multiply(transform);
   }
   return mContext;
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -710,17 +710,18 @@ protected:
  */
 class nsContextBoxBlur {
   typedef mozilla::gfx::Color Color;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
 
 public:
   enum {
-    FORCE_MASK = 0x01
+    FORCE_MASK = 0x01,
+    DISABLE_HARDWARE_ACCELERATION_BLUR = 0x02
   };
   /**
    * Prepares a gfxContext to draw on. Do not call this twice; if you want
    * to get the gfxContext again use GetContext().
    *
    * @param aRect                The coordinates of the surface to create.
    *                             All coordinates must be in app units.
    *                             This must not include the blur radius, pass
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/381746-1-framea.html
@@ -0,0 +1,1 @@
+<body onload="parent.frameOnload();">text
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/381746-1-frameb.html
@@ -0,0 +1,1 @@
+text
--- a/layout/reftests/bugs/381746-1-ref.html
+++ b/layout/reftests/bugs/381746-1-ref.html
@@ -4,12 +4,12 @@
 
 
 
 
 
 </script>
 </head>
 <frameset cols="48%,52%">
-  <frame src="data:text/html;charset=utf-8,text">
-  <frame src="data:text/html;charset=utf-8,text">
+  <frame src="381746-1-frameb.html">
+  <frame src="381746-1-frameb.html">
 </frameset>
 </html>
--- a/layout/reftests/bugs/381746-1.html
+++ b/layout/reftests/bugs/381746-1.html
@@ -1,22 +1,22 @@
 <html class="reftest-wait"><head>
 <title>Testcase Bug 381746 - odd and changing border in frameset</title>
-<script>
-var frameOnloadCalled = false;
-function frameOnload() {
-  if (!frameOnloadCalled) {
-    frameOnloadCalled = true;
-    return;
-  }
-  document.documentElement.removeAttribute("class");
+<script>
+var frameOnloadCalled = false;
+function frameOnload() {
+  if (!frameOnloadCalled) {
+    frameOnloadCalled = true;
+    return;
+  }
+  document.documentElement.removeAttribute("class");
 }
 function doe() {
-document.getElementsByTagName('frame')[0].src = 'data:text/html;charset=utf-8,<body onload="parent.frameOnload();">text';
-document.getElementsByTagName('frame')[1].src = 'data:text/html;charset=utf-8,<body onload="parent.frameOnload();">text';
+document.getElementsByTagName('frame')[0].src = '381746-1-framea.html';
+document.getElementsByTagName('frame')[1].src = '381746-1-framea.html';
 }
 window.onload=doe;
 </script>
 </head>
 <frameset cols="48%,52%">
   <frame src="">
   <frame src="">
 </frameset>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/537507-1-frame.xul
@@ -0,0 +1,1 @@
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'><label value='Here'/></window>
--- a/layout/reftests/bugs/537507-1-ref.xul
+++ b/layout/reftests/bugs/537507-1-ref.xul
@@ -1,8 +1,8 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
 	orient="vertical">
 <label value="The iframe below should show the string 'Here'"/>
-<iframe src="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'&gt;&lt;label value='Here'/&gt;&lt;/window&gt;"></iframe>
+<iframe src="537507-1-frame.xul"></iframe>
 </window>
--- a/layout/reftests/bugs/537507-1.xul
+++ b/layout/reftests/bugs/537507-1.xul
@@ -1,10 +1,8 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
 	orient="vertical">
 <label value="The iframe below should show the string 'Here'"/>
-<iframe src="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'&gt;&lt;label value='Here'/&gt;&lt;/window&gt;"
-	style="display: none"
-	onload="this.style.display = ''"></iframe>
+<iframe src="537507-1-frame.xul" style="display: none" onload="this.style.display = ''"></iframe>
 </window>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/537507-2-frame.xul
@@ -0,0 +1,1 @@
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'><label value='Here'/></window>
--- a/layout/reftests/bugs/537507-2-ref.html
+++ b/layout/reftests/bugs/537507-2-ref.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
 <body>
   The iframe below should show the text 'Here'<br>
-  <iframe src="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'&gt;&lt;label value='Here'/&gt;&lt;/window&gt;"></iframe>
+  <iframe src="537507-2-frame.xul"></iframe>
 </body>
--- a/layout/reftests/bugs/537507-2.html
+++ b/layout/reftests/bugs/537507-2.html
@@ -1,7 +1,5 @@
 <!DOCTYPE html>
 <body>
   The iframe below should show the text 'Here'<br>
-  <iframe src="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'&gt;&lt;label value='Here'/&gt;&lt;/window&gt;"
-	  style="display: none"
-	  onload="this.style.display = ''"></iframe>
+  <iframe src="537507-2-frame.xul" style="display: none" onload="this.style.display = ''"></iframe>
 </body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/858803-1-frame.xhtml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' style='background:yellow'></window>
--- a/layout/reftests/bugs/858803-1.html
+++ b/layout/reftests/bugs/858803-1.html
@@ -1,8 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <body>
-<iframe src="data:application/vnd.mozilla.xul+xml,<?xml version='1.0' encoding='UTF-8'?><window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' style='background:yellow'></window>"
-        style="position:absolute; left:100px; top:100px; width:300px; height:300px; border:1px solid black;">
+<iframe src="858803-1-frame.xhtml" style="position:absolute; left:100px; top:100px; width:300px; height:300px; border:1px solid black;">
 </iframe>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/scrolling/iframe-border-radius-frame.html
@@ -0,0 +1,1 @@
+<body style='font-size:100px; overflow:hidden; background:white;'><p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty
--- a/layout/reftests/scrolling/iframe-border-radius-ref.html
+++ b/layout/reftests/scrolling/iframe-border-radius-ref.html
@@ -1,13 +1,12 @@
 <!DOCTYPE HTML>
 <html class="reftest-wait">
 <body>
-<iframe src="data:text/html,<body style='font-size:100px; overflow:hidden; background:white;'><p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty"
-        id="f" style="width:500px; height:500px; border-radius:100px; border:none;"></iframe>
+<iframe src="iframe-border-radius-frame.html" id="f" style="width:500px; height:500px; border-radius:100px; border:none;"></iframe>
 <script>
 var f = document.getElementById("f");
 function doTest() {
   f.contentWindow.scrollTo(0, 80);
   document.documentElement.removeAttribute('class');
 }
 document.addEventListener("MozReftestInvalidate", doTest);
 </script>
--- a/layout/reftests/scrolling/iframe-border-radius.html
+++ b/layout/reftests/scrolling/iframe-border-radius.html
@@ -1,13 +1,12 @@
 <!DOCTYPE HTML>
 <html class="reftest-wait">
 <body>
-<iframe src="data:text/html,<body style='font-size:100px; overflow:hidden; background:white;'><p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty<p>Hello<p>Kitty"
-        id="f" style="width:500px; height:500px; border-radius:100px; border:none;"></iframe>
+<iframe src="iframe-border-radius-frame.html" id="f" style="width:500px; height:500px; border-radius:100px; border:none;"></iframe>
 <script>
 var f = document.getElementById("f");
 var count = 0;
 function doTest() {
   ++count;
   f.contentWindow.scrollTo(0, count*20);
   if (count == 4) {
     document.documentElement.removeAttribute("class");
--- a/layout/style/crashtests/592698-1.html
+++ b/layout/style/crashtests/592698-1.html
@@ -1,12 +1,12 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
   <iframe id="x"
-          src="data:text/html;charset=utf-8,%3Cdiv%20id%3D%22a%22%3Eaaa"></iframe>
+          srcdoc="<div id='a'>aaa"></iframe>
 
   <script>
     window.onload = function() {
       window.frames[0].document.getElementById("a").setAttribute("style",
         '-moz-transition-property: color;' +
         '-moz-transition-duration: 10s;' +
         'transition-property: color;' +
         'transition-duration: 10s; ' +
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -153,18 +153,19 @@ function run() {
 
     // Test cloning
     var sheet = "@media " + q + " { body { text-decoration: underline } }"
     var sheeturl = "data:text/css," + escape(sheet);
     var link = "<link rel='stylesheet' href='" + sheeturl + "'>";
     var htmldoc = "<!DOCTYPE HTML>" + link + link  + "<body>";
     var docurl = "data:text/html," + escape(htmldoc);
     post_clone_test(docurl, function() {
-      var clonedoc = iframe.contentDocument;
-      var clonewin = iframe.contentWindow;
+      var wrappedFrame = SpecialPowers.wrap(iframe);
+      var clonedoc = wrappedFrame.contentDocument;
+      var clonewin = wrappedFrame.contentWindow;
       var links = clonedoc.getElementsByTagName("link");
       // cause a clone
       var clonedsheet = links[1].sheet;
       clonedsheet.insertRule("#nonexistent { color: purple}", 1);
       // remove the uncloned sheet
       links[0].remove();
 
       var ser3 = clonedsheet.cssRules[0].media.mediaText;
--- a/layout/style/test/test_namespace_rule.html
+++ b/layout/style/test/test_namespace_rule.html
@@ -11,19 +11,19 @@
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 var HTML_NS = "http://www.w3.org/1999/xhtml";
 var style_text;
 
 function run() {
-    var iframe = $("iframe");
-    var ifwin = iframe.contentWindow;
-    var ifdoc = iframe.contentDocument;
+    var wrappedFrame = SpecialPowers.wrap($("iframe"));
+    var ifwin = wrappedFrame.contentWindow;
+    var ifdoc = wrappedFrame.contentDocument;
     var ifbody = ifdoc.getElementsByTagName("body")[0];
 
     function setup_style_text() {
         var style_elem = ifdoc.createElement("style");
         style_elem.setAttribute("type", "text/css");
         ifdoc.getElementsByTagName("head")[0].appendChild(style_elem);
         var style_text = ifdoc.createCDATASection("");
         style_elem.appendChild(style_text);
--- a/layout/style/test/test_property_syntax_errors.html
+++ b/layout/style/test/test_property_syntax_errors.html
@@ -79,17 +79,19 @@ function check_empty_value_rejected(decl
      "empty value '" + property + ":" + emptyval +
      "' is balanced and does not lead to parsing errors afterwards");
   decl.cssText = "";
 }
 
 function run()
 {
   var gDeclaration = document.getElementById("testnode").style;
-  var gQuirksDeclaration = document.getElementById("quirks").contentDocument
+  var quirksFrame = document.getElementById("quirks");
+  var wrappedFrame = SpecialPowers.wrap(quirksFrame);
+  var gQuirksDeclaration = wrappedFrame.contentDocument
                              .getElementById("testnode").style;
 
   for (var property in gCSSProperties) {
     var info = gCSSProperties[property];
 
     check_empty_value_rejected(gDeclaration, "", property);
     check_empty_value_rejected(gDeclaration, " ", property);
 
--- a/layout/style/test/test_selectors.html
+++ b/layout/style/test/test_selectors.html
@@ -129,18 +129,19 @@ function run() {
         var html_doc = "<!DOCTYPE HTML>" +
                        style_sheet_link + style_sheet_link +
                        "<body>";
         if (typeof(body_contents) == "string") {
             html_doc += body_contents;
         }
         var docurl = "data:text/html," + escape(html_doc);
         defer_clonedoc_tests(docurl, function() {
-            var clonedoc = cloneiframe.contentDocument;
-            var clonewin = cloneiframe.contentWindow;
+            var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
+            var clonedoc = wrappedCloneFrame.contentDocument;
+            var clonewin = wrappedCloneFrame.contentWindow;
 
             if (typeof(body_contents) != "string") {
                 body_contents(clonedoc.body);
             }
 
             var links = clonedoc.getElementsByTagName("link");
             // cause a clone
             links[1].sheet.insertRule("#nonexistent { color: purple}", idx + 1);
@@ -216,18 +217,19 @@ function run() {
         var style_sheet_link =
             "<link rel='stylesheet' href='" + style_sheet + "'>";
         var html_doc = "<!DOCTYPE HTML>" +
                        style_sheet_link + style_sheet_link +
                        "<p></p>";
         var docurl = "data:text/html," + escape(html_doc);
 
         defer_clonedoc_tests(docurl, function() {
-            var clonedoc = cloneiframe.contentDocument;
-            var clonewin = cloneiframe.contentWindow;
+            var wrappedCloneFrame = SpecialPowers.wrap(cloneiframe);
+            var clonedoc = wrappedCloneFrame.contentDocument;
+            var clonewin = wrappedCloneFrame.contentWindow;
             var links = clonedoc.getElementsByTagName("link");
             // cause a clone
             links[1].sheet.insertRule("#nonexistent { color: purple}", 0);
             // remove the uncloned sheet
             links[0].remove();
 
             should_match = clonedoc.getElementsByTagName("p")[0];
             is(clonewin.getComputedStyle(should_match).zIndex, String(zi),
--- a/layout/style/test/test_value_cloning.html
+++ b/layout/style/test/test_value_cloning.html
@@ -106,24 +106,26 @@ function doTest()
 function iframe_loaded(event)
 {
   if (event.target != iframe)
     return;
 
   var start_ser = [];
   var start_compute = [];
   var test_cs = [];
-  var ifdoc = iframe.contentDocument;
+  var wrappedFrame = SpecialPowers.wrap(iframe);
+  var ifdoc = wrappedFrame.contentDocument;
+  var ifwin = wrappedFrame.contentWindow;
 
   for (var idx = 0; idx < test_queue.length; ++idx) {
     var current_item = test_queue[idx];
     var info = gCSSProperties[current_item.prop];
 
     var test = ifdoc.getElementById("test" + idx);
-    var cur_cs = iframe.contentWindow.getComputedStyle(test);
+    var cur_cs = ifwin.getComputedStyle(test);
     test_cs.push(cur_cs);
     var cur_ser = ifdoc.styleSheets[0].cssRules[3*idx+2].style.getPropertyValue(current_item.prop);
     if (cur_ser == "") {
       isnot(cur_ser, "",
 	    "serialization should be nonempty for " +
 	    current_item.prop + ": " + current_item.value);
     }
     start_ser.push(cur_ser);
--- a/media/webrtc/trunk/webrtc/modules/desktop_capture/screen_capturer_mac.mm
+++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/screen_capturer_mac.mm
@@ -24,16 +24,17 @@
 #include <dlfcn.h>
 #include <IOKit/pwr_mgt/IOPMLib.h>
 #include <OpenGL/CGLMacro.h>
 #include <OpenGL/OpenGL.h>
 
 #include "webrtc/base/checks.h"
 #include "webrtc/base/constructormagic.h"
 #include "webrtc/base/macutils.h"
+#include "webrtc/base/criticalsection.h"
 #include "webrtc/base/timeutils.h"
 #include "webrtc/modules/desktop_capture/desktop_capturer.h"
 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
 #include "webrtc/modules/desktop_capture/desktop_frame.h"
 #include "webrtc/modules/desktop_capture/desktop_geometry.h"
 #include "webrtc/modules/desktop_capture/desktop_region.h"
 #include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
 #include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h"
@@ -326,16 +327,24 @@ class ScreenCapturerMac : public Desktop
                         const CGRect *rect_array);
   static void ScreenRefreshCallback(CGRectCount count,
                                     const CGRect *rect_array,
                                     void *user_parameter);
   static void ScreenUpdateMoveCallback(CGScreenUpdateMoveDelta delta,
                                        size_t count,
                                        const CGRect *rect_array,
                                        void *user_parameter);
+  struct ScreenCallbackData {
+    explicit ScreenCallbackData(ScreenCapturerMac* capturer)
+              : capturer(capturer) {}
+    rtc::CriticalSection crit_sect_;
+    ScreenCapturerMac* GUARDED_BY(crit_sect_) capturer;
+  };
+
+  ScreenCallbackData* screen_callback_data_;
 #endif
 
   std::unique_ptr<DesktopFrame> CreateFrame();
 
   Callback* callback_ = nullptr;
 
   CGLContextObj cgl_context_ = nullptr;
   ScopedPixelBufferObject pixel_buffer_object_;
@@ -412,26 +421,28 @@ class InvertedDesktopFrame : public Desk
  private:
   std::unique_ptr<DesktopFrame> original_frame_;
 
   RTC_DISALLOW_COPY_AND_ASSIGN(InvertedDesktopFrame);
 };
 
 ScreenCapturerMac::ScreenCapturerMac(
     rtc::scoped_refptr<DesktopConfigurationMonitor> desktop_config_monitor)
-    : desktop_config_monitor_(desktop_config_monitor) {
+    : screen_callback_data_(new ScreenCallbackData(this))
+    , desktop_config_monitor_(desktop_config_monitor) {
 #if defined(MAC_OS_X_VERSION_10_8) && \
   (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8)
   display_stream_manager_ = new DisplayStreamManager;
 #endif
 }
 
 ScreenCapturerMac::~ScreenCapturerMac() {
   ReleaseBuffers();
-  UnregisterRefreshAndMoveHandlers();
+  rtc::CritScope lock(&screen_callback_data_->crit_sect_);
+  screen_callback_data_->capturer = nullptr;
 #if defined(MAC_OS_X_VERSION_10_8) && \
   (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8)
   display_stream_manager_->PrepareForSelfDestruction();
 #endif
   dlclose(app_services_library_);
   dlclose(opengl_library_);
 }
 
@@ -1029,24 +1040,24 @@ bool ScreenCapturerMac::RegisterRefreshA
       CFRunLoopSourceRef source =
           CGDisplayStreamGetRunLoopSource(display_stream);
       CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
       display_stream_manager_->SaveStream(unique_id, display_stream);
     }
   }
 #else
  CGError err = CGRegisterScreenRefreshCallback(
-      ScreenCapturerMac::ScreenRefreshCallback, this);
+      ScreenCapturerMac::ScreenRefreshCallback, screen_callback_data_);
   if (err != kCGErrorSuccess) {
     LOG(LS_ERROR) << "CGRegisterScreenRefreshCallback " << err;
     return false;
   }
 
   err = CGScreenRegisterMoveCallback(
-      ScreenCapturerMac::ScreenUpdateMoveCallback, this);
+      ScreenCapturerMac::ScreenUpdateMoveCallback, screen_callback_data_);
   if (err != kCGErrorSuccess) {
     LOG(LS_ERROR) << "CGScreenRegisterMoveCallback " << err;
     return false;
   }
 #endif
 
   return true;
 }
@@ -1093,31 +1104,53 @@ void ScreenCapturerMac::ScreenUpdateMove
 
   // Currently we just treat move events the same as refreshes.
   ScreenRefresh(count, refresh_rects);
 }
 
 void ScreenCapturerMac::ScreenRefreshCallback(CGRectCount count,
                                               const CGRect* rect_array,
                                               void* user_parameter) {
-  ScreenCapturerMac* capturer =
-      reinterpret_cast<ScreenCapturerMac*>(user_parameter);
-  if (capturer->screen_pixel_bounds_.is_empty())
-    capturer->ScreenConfigurationChanged();
-  capturer->ScreenRefresh(count, rect_array);
+  ScreenCallbackData* screen_callback_data =
+      reinterpret_cast<ScreenCallbackData*>(user_parameter);
+
+  rtc::CritScope lock(&screen_callback_data->crit_sect_);
+  if (!screen_callback_data->capturer) {
+    CGUnregisterScreenRefreshCallback(
+        ScreenCapturerMac::ScreenRefreshCallback, screen_callback_data);
+    CGScreenUnregisterMoveCallback(
+        ScreenCapturerMac::ScreenUpdateMoveCallback, screen_callback_data);
+    delete screen_callback_data;
+    return;
+  }
+
+  if (screen_callback_data->capturer->screen_pixel_bounds_.is_empty())
+    screen_callback_data->capturer->ScreenConfigurationChanged();
+  screen_callback_data->capturer->ScreenRefresh(count, rect_array);
 }
 
 void ScreenCapturerMac::ScreenUpdateMoveCallback(
     CGScreenUpdateMoveDelta delta,
     size_t count,
     const CGRect* rect_array,
     void* user_parameter) {
-  ScreenCapturerMac* capturer =
-      reinterpret_cast<ScreenCapturerMac*>(user_parameter);
-  capturer->ScreenUpdateMove(delta, count, rect_array);
+  ScreenCallbackData* screen_callback_data =
+      reinterpret_cast<ScreenCallbackData*>(user_parameter);
+
+  rtc::CritScope lock(&screen_callback_data->crit_sect_);
+  if (!screen_callback_data->capturer) {
+    CGUnregisterScreenRefreshCallback(
+        ScreenCapturerMac::ScreenRefreshCallback, screen_callback_data);
+    CGScreenUnregisterMoveCallback(
+        ScreenCapturerMac::ScreenUpdateMoveCallback, screen_callback_data);
+    delete screen_callback_data;
+    return;
+  }
+
+  screen_callback_data->capturer->ScreenUpdateMove(delta, count, rect_array);
 }
 #endif
 
 std::unique_ptr<DesktopFrame> ScreenCapturerMac::CreateFrame() {
   std::unique_ptr<DesktopFrame> frame(
       new BasicDesktopFrame(screen_pixel_bounds_.size()));
   frame->set_dpi(DesktopVector(kStandardDPI * dip_to_pixel_scale_,
                                kStandardDPI * dip_to_pixel_scale_));
--- a/modules/libjar/nsJAR.cpp
+++ b/modules/libjar/nsJAR.cpp
@@ -320,29 +320,29 @@ nsJAR::GetInputStream(const nsACString &
 NS_IMETHODIMP
 nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
                           const nsACString &aEntryName, nsIInputStream** result)
 {
   NS_ENSURE_ARG_POINTER(result);
 
   // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
   nsZipItem *item = nullptr;
-  const char *entry = PromiseFlatCString(aEntryName).get();
-  if (*entry) {
+  const nsCString& entry = PromiseFlatCString(aEntryName);
+  if (*entry.get()) {
     // First check if item exists in jar
-    item = mZip->GetItem(entry);
+    item = mZip->GetItem(entry.get());
     if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
   }
   nsJARInputStream* jis = new nsJARInputStream();
   // addref now so we can call InitFile/InitDirectory()
   NS_ADDREF(*result = jis);
 
   nsresult rv = NS_OK;
   if (!item || item->IsDirectory()) {
-    rv = jis->InitDirectory(this, aJarDirSpec, entry);
+    rv = jis->InitDirectory(this, aJarDirSpec, entry.get());
   } else {
     rv = jis->InitFile(this, item);
   }
   if (NS_FAILED(rv)) {
     NS_RELEASE(*result);
   }
   return rv;
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5756,16 +5756,22 @@ pref("security.mixed_content.hsts_primin
 
 // TODO: Bug 1324406: Treat 'data:' documents as unique, opaque origins
 // If true, data: URIs will be treated as unique opaque origins, hence will use
 // a NullPrincipal as the security context.
 // Otherwise it will inherit the origin from parent node, this is the legacy
 // behavior of Firefox.
 pref("security.data_uri.unique_opaque_origin", false);
 
+// TODO: Bug 1380959: Block toplevel data: URI navigations
+// If true, all toplevel data: URI navigations will be blocked.
+// Please note that manually entering a data: URI in the
+// URL-Bar will not be blocked when flipping this pref.
+pref("security.data_uri.block_toplevel_data_uri_navigations", false);
+
 // Disable Storage api in release builds.
 #if defined(NIGHTLY_BUILD) && !defined(MOZ_WIDGET_ANDROID)
 pref("dom.storageManager.enabled", true);
 #else
 pref("dom.storageManager.enabled", false);
 #endif
 
 pref("dom.storageManager.prompt.testing", false);
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -162,16 +162,17 @@ static const char kProfileChangeNetTeard
 static const char kProfileChangeNetRestoreTopic[] = "profile-change-net-restore";
 static const char kProfileDoChange[] = "profile-do-change";
 
 // Necko buffer defaults
 uint32_t   nsIOService::gDefaultSegmentSize = 4096;
 uint32_t   nsIOService::gDefaultSegmentCount = 24;
 
 bool nsIOService::sIsDataURIUniqueOpaqueOrigin = false;
+bool nsIOService::sBlockToplevelDataUriNavigations = false;
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsIOService::nsIOService()
     : mOffline(true)
     , mOfflineForProfileChange(false)
     , mManageLinkStatus(false)
     , mConnectivity(true)
@@ -245,16 +246,18 @@ nsIOService::Init()
         observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
         observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
     }
     else
         NS_WARNING("failed to get observer service");
 
     Preferences::AddBoolVarCache(&sIsDataURIUniqueOpaqueOrigin,
                                  "security.data_uri.unique_opaque_origin", false);
+    Preferences::AddBoolVarCache(&sBlockToplevelDataUriNavigations,
+                                 "security.data_uri.block_toplevel_data_uri_navigations", false);
     Preferences::AddBoolVarCache(&mOfflineMirrorsConnectivity, OFFLINE_MIRRORS_CONNECTIVITY, true);
 
     gIOService = this;
 
     InitializeNetworkLinkService();
 
     SetOffline(false);
 
@@ -1926,10 +1929,16 @@ nsIOService::SpeculativeAnonymousConnect
 }
 
 /*static*/ bool
 nsIOService::IsDataURIUniqueOpaqueOrigin()
 {
   return sIsDataURIUniqueOpaqueOrigin;
 }
 
+/*static*/ bool
+nsIOService::BlockToplevelDataUriNavigations()
+{
+  return sBlockToplevelDataUriNavigations;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/base/nsIOService.h
+++ b/netwerk/base/nsIOService.h
@@ -91,16 +91,17 @@ public:
     // caused problems (bug 1242755) so we doing it in this way.
     // As soon as nsIOService gets notification that it is shutdown it is going to
     // reset mHttpHandlerAlreadyShutingDown.
     void SetHttpHandlerAlreadyShutingDown();
 
     bool IsLinkUp();
 
     static bool IsDataURIUniqueOpaqueOrigin();
+    static bool BlockToplevelDataUriNavigations();
 
     // Used to count the total number of HTTP requests made
     void IncrementRequestNumber() { mTotalRequests++; }
     uint32_t GetTotalRequestNumber() { return mTotalRequests; }
     // Used to keep "race cache with network" stats
     void IncrementCacheWonRequestNumber() { mCacheWon++; }
     uint32_t GetCacheWonRequestNumber() { return mCacheWon; }
     void IncrementNetWonRequestNumber() { mNetWon++; }
@@ -181,16 +182,17 @@ private:
     // cached categories
     nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
 
     nsTArray<int32_t>                    mRestrictedPortList;
 
     bool                                 mNetworkNotifyChanged;
 
     static bool                          sIsDataURIUniqueOpaqueOrigin;
+    static bool                          sBlockToplevelDataUriNavigations;
 
     uint32_t mTotalRequests;
     uint32_t mCacheWon;
     uint32_t mNetWon;
 
     // These timestamps are needed for collecting telemetry on PR_Connect,
     // PR_ConnectContinue and PR_Close blocking time.  If we spend very long
     // time in any of these functions we want to know if and what network
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -525,21 +525,28 @@ nsHttpChannel::Connect()
             if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
                 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
             }
             return NS_ERROR_DOCUMENT_NOT_CACHED;
         }
         // otherwise, let's just proceed without using the cache.
     }
 
+    if (mRaceCacheWithNetwork && mCacheEntry && !mCachedContentIsValid &&
+        (mDidReval || mCachedContentIsPartial)) {
+        // We won't send the conditional request because the unconditional
+        // request was already sent (see bug 1377223).
+        AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
+    }
+
     // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
     // returns, then we may not have started reading from the cache.
     // If the content is valid, we should attempt to do so, as technically the
     // cache has won the race.
-    if (sRCWNEnabled && mCachedContentIsValid && mNetworkTriggered) {
+    if (mRaceCacheWithNetwork && mCachedContentIsValid) {
         Unused << ReadFromCache(true);
     }
 
     return TriggerNetwork(0);
 }
 
 nsresult
 nsHttpChannel::TryHSTSPriming()
@@ -2397,16 +2404,17 @@ nsHttpChannel::ContinueProcessResponse2(
         return NS_OK;
     }
 
     rv = NS_OK;
 
     uint32_t httpStatus = mResponseHead->Status();
 
     bool successfulReval = false;
+    bool partialContentUsed = false;
 
     // handle different server response categories.  Note that we handle
     // caching or not caching of error pages in
     // nsHttpResponseHead::MustValidate; if you change this switch, update that
     // one
     switch (httpStatus) {
     case 200:
     case 203:
@@ -2420,19 +2428,22 @@ nsHttpChannel::ContinueProcessResponse2(
             rv = CallOnStartRequest();
             break;
         }
         // these can normally be cached
         rv = ProcessNormal();
         MaybeInvalidateCacheEntryForSubsequentGet();
         break;
     case 206:
-        if (mCachedContentIsPartial) // an internal byte range request...
+        if (mCachedContentIsPartial) { // an internal byte range request...
             rv = ProcessPartialContent();
-        else {
+            if (NS_SUCCEEDED(rv)) {
+                partialContentUsed = true;
+            }
+        } else {
             mCacheInputStream.CloseAndRelease();
             rv = ProcessNormal();
         }
         break;
     case 300:
     case 301:
     case 302:
     case 307:
@@ -2540,16 +2551,26 @@ nsHttpChannel::ContinueProcessResponse2(
         }
         break;
     default:
         rv = ProcessNormal();
         MaybeInvalidateCacheEntryForSubsequentGet();
         break;
     }
 
+    if (mRaceDelay && !mRaceCacheWithNetwork &&
+        (mCachedContentIsPartial || mDidReval)) {
+        if (successfulReval || partialContentUsed) {
+            AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
+        } else {
+            AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentNotUsed);
+        }
+    }
+
+
     if (gHttpHandler->IsTelemetryEnabled()) {
         CacheDisposition cacheDisposition;
         if (!mDidReval) {
             cacheDisposition = kCacheMissed;
         } else if (successfulReval) {
             cacheDisposition = kCacheHitViaReval;
         } else {
             cacheDisposition = kCacheMissedViaReval;
@@ -4525,17 +4546,24 @@ nsHttpChannel::OnCacheEntryAvailableInte
         return rv;
     }
 
     // We may be waiting for more callbacks...
     if (AwaitingCacheCallbacks()) {
         return NS_OK;
     }
 
-    if (mCachedContentIsValid && mNetworkTriggered) {
+    if (mRaceCacheWithNetwork && mCacheEntry && !mCachedContentIsValid &&
+        (mDidReval || mCachedContentIsPartial)) {
+        // We won't send the conditional request because the unconditional
+        // request was already sent (see bug 1377223).
+        AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
+    }
+
+    if (mRaceCacheWithNetwork && mCachedContentIsValid) {
         Unused << ReadFromCache(true);
     }
 
     return TriggerNetwork(0);
 }
 
 nsresult
 nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
--- a/parser/htmlparser/tests/crashtests/445171-1-inner.svg
+++ b/parser/htmlparser/tests/crashtests/445171-1-inner.svg
@@ -1,5 +1,5 @@
-<svg xmlns="http://www.w3.org/2000/svg" onload="location = 'data:text/html,&lt;script&gt;parent.done()&lt;/script&gt;';">
+<svg xmlns="http://www.w3.org/2000/svg" onload="location = 'file_445171-1.html'">
 
 <rect x="5" y="5" width="50" height="50" />
 
 </svg>
new file mode 100644
--- /dev/null
+++ b/parser/htmlparser/tests/crashtests/file_445171-1.html
@@ -0,0 +1,1 @@
+<script>parent.done()</script>
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1710,33 +1710,34 @@ InitializeNSSWithFallbacks(const nsACStr
 {
   if (nocertdb || profilePath.IsEmpty()) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("nocertdb mode or empty profile path -> NSS_NoDB_Init"));
     SECStatus srv = NSS_NoDB_Init(nullptr);
     return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
   }
 
-  const char* profilePathCStr = PromiseFlatCString(profilePath).get();
+
+  const nsCString& profilePathCStr = PromiseFlatCString(profilePath);
   // Try read/write mode. If we're in safeMode, we won't load PKCS#11 modules.
 #ifndef ANDROID
   PRErrorCode savedPRErrorCode1;
 #endif // ifndef ANDROID
-  SECStatus srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false,
+  SECStatus srv = ::mozilla::psm::InitializeNSS(profilePathCStr.get(), false,
                                                 !safeMode);
   if (srv == SECSuccess) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r/w mode"));
     return NS_OK;
   }
 #ifndef ANDROID
   savedPRErrorCode1 = PR_GetError();
   PRErrorCode savedPRErrorCode2;
 #endif // ifndef ANDROID
   // That failed. Try read-only mode.
-  srv = ::mozilla::psm::InitializeNSS(profilePathCStr, true, !safeMode);
+  srv = ::mozilla::psm::InitializeNSS(profilePathCStr.get(), true, !safeMode);
   if (srv == SECSuccess) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r-o mode"));
     return NS_OK;
   }
 #ifndef ANDROID
   savedPRErrorCode2 = PR_GetError();
 #endif // ifndef ANDROID
 
@@ -1747,38 +1748,38 @@ InitializeNSSWithFallbacks(const nsACStr
   if (!safeMode && (savedPRErrorCode1 == SEC_ERROR_LEGACY_DATABASE ||
                     savedPRErrorCode2 == SEC_ERROR_LEGACY_DATABASE)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("attempting no-module db init"));
     // It would make sense to initialize NSS in read-only mode here since this
     // is just a test to see if the PKCS#11 module DB being in FIPS mode is the
     // problem, but for some reason the combination of read-only and no-moddb
     // flags causes NSS initialization to fail, so unfortunately we have to use
     // read-write mode.
-    srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false, false);
+    srv = ::mozilla::psm::InitializeNSS(profilePathCStr.get(), false, false);
     if (srv == SECSuccess) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FIPS may be the problem"));
       // Unload NSS so we can attempt to fix this situation for the user.
       srv = NSS_Shutdown();
       if (srv != SECSuccess) {
         return NS_ERROR_FAILURE;
       }
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("trying to rename module db"));
       // If this fails non-catastrophically, we'll attempt to initialize NSS
       // again in r/w then r-o mode (both of which will fail), and then we'll
       // fall back to NSS_NoDB_Init, which is the behavior we want.
       nsresult rv = AttemptToRenamePKCS11ModuleDB(profilePath);
       if (NS_FAILED(rv)) {
         return rv;
       }
-      srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false, true);
+      srv = ::mozilla::psm::InitializeNSS(profilePathCStr.get(), false, true);
       if (srv == SECSuccess) {
         MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r/w mode"));
         return NS_OK;
       }
-      srv = ::mozilla::psm::InitializeNSS(profilePathCStr, true, true);
+      srv = ::mozilla::psm::InitializeNSS(profilePathCStr.get(), true, true);
       if (srv == SECSuccess) {
         MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r-o mode"));
         return NS_OK;
       }
     }
   }
 #endif
 
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -680,26 +680,28 @@ SandboxEarlyInit(GeckoProcessType aType)
 #ifdef MOZ_CONTENT_SANDBOX
 /**
  * Starts the seccomp sandbox for a content process.  Should be called
  * only once, and before any potentially harmful content is loaded.
  *
  * Will normally make the process exit on failure.
 */
 bool
-SetContentProcessSandbox(int aBrokerFd, std::vector<int>& aSyscallWhitelist)
+SetContentProcessSandbox(int aBrokerFd, bool aFileProcess,
+                         std::vector<int>& aSyscallWhitelist)
 {
   if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
     if (aBrokerFd >= 0) {
       close(aBrokerFd);
     }
     return false;
   }
 
-  gSandboxReporterClient.emplace(SandboxReport::ProcType::CONTENT);
+  gSandboxReporterClient.emplace(aFileProcess ? SandboxReport::ProcType::FILE
+                                              : SandboxReport::ProcType::CONTENT);
 
   // This needs to live until the process exits.
   static Maybe<SandboxBrokerClient> sBroker;
   if (aBrokerFd >= 0) {
     sBroker.emplace(aBrokerFd);
   }
 
   SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr),
--- a/security/sandbox/linux/Sandbox.h
+++ b/security/sandbox/linux/Sandbox.h
@@ -20,17 +20,19 @@ namespace mozilla {
 // This must be called early, while the process is still single-threaded.
 MOZ_EXPORT void SandboxEarlyInit(GeckoProcessType aType);
 
 #ifdef MOZ_CONTENT_SANDBOX
 // Call only if SandboxInfo::CanSandboxContent() returns true.
 // (No-op if MOZ_DISABLE_CONTENT_SANDBOX is set.)
 // aBrokerFd is the filesystem broker client file descriptor,
 // or -1 to allow direct filesystem access.
+// isFileProcess determines whether we allow system wide file reads.
 MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd,
+                                         bool aFileProcess,
                                          std::vector<int>& aSyscallWhitelist);
 #endif
 
 #ifdef MOZ_GMP_SANDBOX
 // Call only if SandboxInfo::CanSandboxMedia() returns true.
 // (No-op if MOZ_DISABLE_GMP_SANDBOX is set.)
 // aFilePath is the path to the plugin file.
 MOZ_EXPORT void SetMediaPluginSandbox(const char *aFilePath);
--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -138,17 +138,17 @@ SandboxBrokerClient::DoCall(const Reques
     }
     return resp.mError;
   }
   if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
     // Keep in mind that "rejected" files can include ones that don't
     // actually exist, if it's something that's optional or part of a
     // search path (e.g., shared libraries).  In those cases, this
     // error message is expected.
-    SANDBOX_LOG_ERROR("Rejected errno %d op %d flags 0%o path %s",
+    SANDBOX_LOG_ERROR("Failed errno %d op %d flags 0%o path %s",
                       resp.mError, aReq->mOp, aReq->mFlags, path);
   }
   if (openedFd >= 0) {
     close(openedFd);
   }
   return resp.mError;
 }
 
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -16,16 +16,17 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #ifdef XP_LINUX
 #include <sys/prctl.h>
 #endif
 
+#include "base/string_util.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 #include "mozilla/NullPtr.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/ipc/FileDescriptor.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
 
@@ -195,24 +196,30 @@ SandboxBroker::Policy::AddDir(int aPerms
   if (stat(aPath, &statBuf) != 0) {
     return;
   }
 
   if (!S_ISDIR(statBuf.st_mode)) {
     return;
   }
 
+  // Add a Prefix permission on things inside the dir.
   nsDependentCString path(aPath);
   MOZ_ASSERT(path.Length() <= kMaxPathLen - 1);
   // Enforce trailing / on aPath
-  if (path[path.Length() - 1] != '/') {
+  if (path.Last() != '/') {
     path.Append('/');
   }
+  Policy::AddPrefixInternal(aPerms, path);
 
-  Policy::AddPrefixInternal(aPerms, path);
+  // Add a path permission on the dir itself so it can
+  // be opened. We're guaranteed to have a trailing / now,
+  // so just cut that.
+  path.Truncate(path.Length() - 1);
+  Policy::AddPath(aPerms, path.get(), AddAlways);
 }
 
 void
 SandboxBroker::Policy::AddPrefix(int aPerms, const char* aPath)
 {
   Policy::AddPrefixInternal(aPerms, nsDependentCString(aPath));
 }
 
@@ -418,27 +425,84 @@ DoLink(const char* aPath, const char* aP
 }
 
 size_t
 SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen)
 {
   if (strstr(aPath, "..") != nullptr) {
     char* result = realpath(aPath, nullptr);
     if (result != nullptr) {
-      strncpy(aPath, result, aBufSize);
-      aPath[aBufSize - 1] = '\0';
+      base::strlcpy(aPath, result, aBufSize);
       free(result);
       // Size changed, but guaranteed to be 0 terminated
       aPathLen = strlen(aPath);
     }
     // ValidatePath will handle failure to translate
   }
   return aPathLen;
 }
 
+nsCString
+SandboxBroker::ReverseSymlinks(const nsACString& aPath)
+{
+  // Revert any symlinks we previously resolved.
+  int32_t cutLength = aPath.Length();
+  nsCString cutPath(Substring(aPath, 0, cutLength));
+
+  for (;;) {
+    nsCString orig;
+    bool found = mSymlinkMap.Get(cutPath, &orig);
+    if (found) {
+      orig.Append(Substring(aPath, cutLength, aPath.Length() - cutLength));
+      return orig;
+    }
+    // Not found? Remove a path component and try again.
+    int32_t pos = cutPath.RFindChar('/');
+    if (pos == kNotFound || pos <= 0) {
+      // will be empty
+      return orig;
+    } else {
+      // Cut until just before the /
+      cutLength = pos;
+      cutPath.Assign(Substring(cutPath, 0, cutLength));
+    }
+  }
+}
+
+int
+SandboxBroker::SymlinkPermissions(const char* aPath, const size_t aPathLen)
+{
+  // Work on a temporary copy, so we can reverse it.
+  // Because we bail on a writable dir, SymlinkPath
+  // might not restore the callers' path exactly.
+  char pathBufSymlink[kMaxPathLen + 1];
+  strcpy(pathBufSymlink, aPath);
+
+  nsCString orig = ReverseSymlinks(nsDependentCString(pathBufSymlink, aPathLen));
+  if (!orig.IsEmpty()) {
+    if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+      SANDBOX_LOG_ERROR("Reversing %s -> %s", aPath, orig.get());
+    }
+    base::strlcpy(pathBufSymlink, orig.get(), sizeof(pathBufSymlink));
+  }
+
+  int perms = 0;
+  // Resolve relative paths, propagate permissions and
+  // fail if a symlink is in a writable path. The output is in perms.
+  char* result = SandboxBroker::SymlinkPath(mPolicy.get(), pathBufSymlink, NULL, &perms);
+  if (result != NULL) {
+    free(result);
+    // We finished the translation, so we have a usable return in "perms".
+    return perms;
+  } else {
+    // Empty path means we got a writable dir in the chain.
+    return 0;
+  }
+}
+
 void
 SandboxBroker::ThreadMain(void)
 {
   char threadName[16];
   SprintfLiteral(threadName, "FS Broker %d", mChildPid);
   PlatformThread::SetName(threadName);
 
   // Permissive mode can only be enabled through an environment variable,
@@ -447,18 +511,18 @@ SandboxBroker::ThreadMain(void)
   bool permissive = SandboxInfo::Get().Test(SandboxInfo::kPermissive);
 
   while (true) {
     struct iovec ios[2];
     // We will receive the path strings in 1 buffer and split them back up.
     char recvBuf[2 * (kMaxPathLen + 1)];
     char pathBuf[kMaxPathLen + 1];
     char pathBuf2[kMaxPathLen + 1];
-    size_t pathLen;
-    size_t pathLen2;
+    size_t pathLen = 0;
+    size_t pathLen2 = 0;
     char respBuf[kMaxPathLen + 1]; // Also serves as struct stat
     Request req;
     Response resp;
     int respfd;
 
     // Make sure stat responses fit in the response buffer
     MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat));
 
@@ -533,16 +597,28 @@ SandboxBroker::ThreadMain(void)
 
       // First string is guaranteed to be 0-terminated.
       pathLen = first_len;
 
       // Look up the first pathname but first translate relative paths.
       pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), pathLen);
       perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
 
+      // We don't have read permissions on the requested dir.
+      // Did we arrive from a symlink in a path that is not writable?
+      // Then try to figure out the original path and see if that is readable.
+      if (!(perms & MAY_READ)) {
+          // Work on the original path,
+          // this reverses ConvertToRealPath above.
+          int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
+          if (symlinkPerms > 0) {
+            perms = symlinkPerms;
+          }
+      }
+
       // Same for the second path.
       pathLen2 = strnlen(pathBuf2, kMaxPathLen);
       if (pathLen2 > 0) {
         // Force 0 termination.
         pathBuf2[pathLen2] = '\0';
         pathLen2 = ConvertToRealPath(pathBuf2, sizeof(pathBuf2), pathLen2);
         int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2));
 
@@ -693,16 +769,44 @@ SandboxBroker::ThreadMain(void)
           AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
         }
         break;
 
       case SANDBOX_FILE_READLINK:
         if (permissive || AllowOperation(R_OK, perms)) {
           ssize_t respSize = readlink(pathBuf, (char*)&respBuf, sizeof(respBuf));
           if (respSize >= 0) {
+              if (respSize > 0) {
+              // Record the mapping so we can invert the file to the original
+              // symlink.
+              nsDependentCString orig(pathBuf, pathLen);
+              nsDependentCString xlat(respBuf, respSize);
+              if (!orig.Equals(xlat) && xlat[0] == '/') {
+                if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+                  SANDBOX_LOG_ERROR("Recording mapping %s -> %s",
+                                    xlat.get(), orig.get());
+                }
+                mSymlinkMap.Put(xlat, orig);
+              }
+              // Make sure we can invert a fully resolved mapping too. If our
+              // caller is realpath, and there's a relative path involved, the
+              // client side will try to open this one.
+              char *resolvedBuf = realpath(pathBuf, nullptr);
+              if (resolvedBuf) {
+                nsDependentCString resolvedXlat(resolvedBuf);
+                if (!orig.Equals(resolvedXlat) && !xlat.Equals(resolvedXlat)) {
+                  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+                    SANDBOX_LOG_ERROR("Recording mapping %s -> %s",
+                                      resolvedXlat.get(), orig.get());
+                  }
+                  mSymlinkMap.Put(resolvedXlat, orig);
+                }
+                free(resolvedBuf);
+              }
+            }
             resp.mError = respSize;
             ios[1].iov_base = &respBuf;
             ios[1].iov_len = respSize;
           } else {
             resp.mError = -errno;
           }
         } else {
           AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
@@ -742,16 +846,16 @@ SandboxBroker::AuditPermissive(int aOp, 
                     " permissive=1 error=\"%s\"", aOp, aFlags, aPerms,
                     aPath, mChildPid, strerror(errno));
 }
 
 void
 SandboxBroker::AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath)
 {
   if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
-    SANDBOX_LOG_ERROR("SandboxBroker: denied op=%d rflags=%o perms=%d path=%s for pid=%d" \
-                      " error=\"%s\"", aOp, aFlags, aPerms, aPath, mChildPid,
-                      strerror(errno));
+    SANDBOX_LOG_ERROR("SandboxBroker: denied op=%s rflags=%o perms=%d path=%s for pid=%d",
+                      OperationDescription[aOp], aFlags,
+                      aPerms, aPath, mChildPid);
   }
 }
 
 
 } // namespace mozilla
--- a/security/sandbox/linux/broker/SandboxBroker.h
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -51,20 +51,20 @@ class SandboxBroker final
     // (This overrides all other flags.)
     CRASH_INSTEAD = 1 << 4,
     // Applies to everything below this path, including subdirs created
     // at runtime
     RECURSIVE     = 1 << 5,
   };
   // Bitwise operations on enum values return ints, so just use int in
   // the hash table type (and below) to avoid cluttering code with casts.
-  typedef nsDataHashtable<nsCStringHashKey, int> PathMap;
+  typedef nsDataHashtable<nsCStringHashKey, int> PathPermissionMap;
 
   class Policy {
-    PathMap mMap;
+    PathPermissionMap mMap;
   public:
     Policy();
     Policy(const Policy& aOther);
     ~Policy();
 
     enum AddCondition {
       AddIfExistsNow,
       AddAlways,
@@ -115,23 +115,32 @@ class SandboxBroker final
   virtual ~SandboxBroker();
 
  private:
   PlatformThreadHandle mThread;
   int mFileDesc;
   const int mChildPid;
   const UniquePtr<const Policy> mPolicy;
 
+  typedef nsDataHashtable<nsCStringHashKey, nsCString> PathMap;
+  PathMap mSymlinkMap;
+
   SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid,
                 int& aClientFd);
   void ThreadMain(void) override;
   void AuditPermissive(int aOp, int aFlags, int aPerms, const char* aPath);
   void AuditDenial(int aOp, int aFlags, int aPerms, const char* aPath);
   // Remap relative paths to absolute paths.
   size_t ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen);
+  nsCString ReverseSymlinks(const nsACString& aPath);
+  // Retrieves permissions for the path the original symlink sits in.
+  int SymlinkPermissions(const char* aPath, const size_t aPathLen);
+  // In SandboxBrokerRealPath.cpp
+  char* SymlinkPath(const Policy* aPolicy, const char* __restrict aPath,
+                    char* __restrict aResolved, int* aPermission);
 
   // Holding a UniquePtr should disallow copying, but to make that explicit:
   SandboxBroker(const SandboxBroker&) = delete;
   void operator=(const SandboxBroker&) = delete;
 };
 
 } // namespace mozilla
 
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
@@ -25,16 +25,30 @@
 // In the future, if the broker becomes a dedicated executable, this
 // can change.
 #error "No MSG_CMSG_CLOEXEC?"
 #endif // XP_LINUX
 #endif // MSG_CMSG_CLOEXEC
 
 namespace mozilla {
 
+const char* SandboxBrokerCommon::OperationDescription[] = {
+  "open",
+  "access",
+  "stat",
+  "chmod",
+  "link",
+  "symlink",
+  "mkdir",
+  "rename",
+  "rmdir",
+  "unlink",
+  "readlink"
+};
+
 /* static */ ssize_t
 SandboxBrokerCommon::RecvWithFd(int aFd, const iovec* aIO, size_t aNumIO,
                                     int* aPassedFdPtr)
 {
   struct msghdr msg = {};
   msg.msg_iov = const_cast<iovec*>(aIO);
   msg.msg_iovlen = aNumIO;
 
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -33,16 +33,18 @@ public:
     SANDBOX_FILE_LINK,
     SANDBOX_FILE_SYMLINK,
     SANDBOX_FILE_MKDIR,
     SANDBOX_FILE_RENAME,
     SANDBOX_FILE_RMDIR,
     SANDBOX_FILE_UNLINK,
     SANDBOX_FILE_READLINK,
   };
+  // String versions of the above
+  static const char* OperationDescription[];
 
   struct Request {
     Operation mOp;
     // For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
     int mFlags;
     // Size of return value buffer, if any
     size_t mBufSize;
     // The rest of the packet is the pathname.
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -6,20 +6,23 @@
 
 #include "SandboxBrokerPolicyFactory.h"
 #include "SandboxInfo.h"
 #include "SandboxLogging.h"
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SandboxSettings.h"
+#include "mozilla/dom/ContentChild.h"
 #include "nsPrintfCString.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
 #include "SpecialSystemDirectory.h"
 
 #ifdef ANDROID
 #include "cutils/properties.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK
 #include <glib.h>
@@ -37,24 +40,26 @@ static const int rdwrcr = rdwr | Sandbox
 #endif
 
 SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory()
 {
   // Policy entries that are the same in every process go here, and
   // are cached over the lifetime of the factory.
 #if defined(MOZ_CONTENT_SANDBOX)
   SandboxBroker::Policy* policy = new SandboxBroker::Policy;
-  policy->AddDir(rdonly, "/");
   policy->AddDir(rdwrcr, "/dev/shm");
+  // Write permssions
+  //
   // Add write permissions on the temporary directory. This can come
   // from various environment variables (TMPDIR,TMP,TEMP,...) so
   // make sure to use the full logic.
   nsCOMPtr<nsIFile> tmpDir;
   nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
                                           getter_AddRefs(tmpDir));
+
   if (NS_SUCCEEDED(rv)) {
     nsAutoCString tmpPath;
     rv = tmpDir->GetNativePath(tmpPath);
     if (NS_SUCCEEDED(rv)) {
       policy->AddDir(rdwrcr, tmpPath.get());
     }
   }
   // If the above fails at any point, fall back to a very good guess.
@@ -77,48 +82,188 @@ SandboxBrokerPolicyFactory::SandboxBroke
   // Bug 1321134: DConf's single bit of shared memory
   if (const auto userDir = g_get_user_runtime_dir()) {
     // The leaf filename is "user" by default, but is configurable.
     nsPrintfCString shmPath("%s/dconf/", userDir);
     policy->AddPrefix(rdwrcr, shmPath.get());
   }
 #endif
 
+  // Read permissions
+  // No read blocking at level 2 and below
+  if (Preferences::GetInt("security.sandbox.content.level") <= 2) {
+    policy->AddDir(rdonly, "/");
+    mCommonContentPolicy.reset(policy);
+    return;
+  }
+  policy->AddPath(rdonly, "/dev/urandom");
+  policy->AddPath(rdonly, "/proc/cpuinfo");
+  policy->AddPath(rdonly, "/proc/meminfo");
+  policy->AddDir(rdonly, "/lib");
+  policy->AddDir(rdonly, "/etc");
+  policy->AddDir(rdonly, "/usr/share");
+  policy->AddDir(rdonly, "/usr/local/share");
+  policy->AddDir(rdonly, "/usr/lib");
+  policy->AddDir(rdonly, "/usr/lib32");
+  policy->AddDir(rdonly, "/usr/lib64");
+  policy->AddDir(rdonly, "/usr/X11R6/lib/X11/fonts");
+  policy->AddDir(rdonly, "/usr/tmp");
+  policy->AddDir(rdonly, "/var/tmp");
+  policy->AddDir(rdonly, "/sys/devices/cpu");
+  policy->AddDir(rdonly, "/sys/devices/system/cpu");
+
+  // Configuration dirs in the homedir that we want to allow read
+  // access to.
+  mozilla::Array<const char*, 3> confDirs = {
+    ".config",
+    ".themes",
+    ".fonts",
+  };
+
+  nsCOMPtr<nsIFile> homeDir;
+  rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
+  if (NS_SUCCEEDED(rv)) {
+    nsCOMPtr<nsIFile> confDir;
+
+    for (auto dir : confDirs) {
+      rv = homeDir->Clone(getter_AddRefs(confDir));
+      if (NS_SUCCEEDED(rv)) {
+        rv = confDir->AppendNative(nsDependentCString(dir));
+        if (NS_SUCCEEDED(rv)) {
+          nsAutoCString tmpPath;
+          rv = confDir->GetNativePath(tmpPath);
+          if (NS_SUCCEEDED(rv)) {
+            policy->AddDir(rdonly, tmpPath.get());
+          }
+        }
+      }
+    }
+
+    // ~/.local/share (for themes)
+    rv = homeDir->Clone(getter_AddRefs(confDir));
+    if (NS_SUCCEEDED(rv)) {
+      rv = confDir->AppendNative(NS_LITERAL_CSTRING(".local"));
+      if (NS_SUCCEEDED(rv)) {
+        rv = confDir->AppendNative(NS_LITERAL_CSTRING("share"));
+      }
+      if (NS_SUCCEEDED(rv)) {
+        nsAutoCString tmpPath;
+        rv = confDir->GetNativePath(tmpPath);
+        if (NS_SUCCEEDED(rv)) {
+          policy->AddDir(rdonly, tmpPath.get());
+        }
+      }
+    }
+
+    // ~/.fonts.conf (Fontconfig)
+    rv = homeDir->Clone(getter_AddRefs(confDir));
+    if (NS_SUCCEEDED(rv)) {
+      rv = confDir->AppendNative(NS_LITERAL_CSTRING(".fonts.conf"));
+      if (NS_SUCCEEDED(rv)) {
+        nsAutoCString tmpPath;
+        rv = confDir->GetNativePath(tmpPath);
+        if (NS_SUCCEEDED(rv)) {
+          policy->AddPath(rdonly, tmpPath.get());
+        }
+      }
+    }
+
+    // .pangorc
+    rv = homeDir->Clone(getter_AddRefs(confDir));
+    if (NS_SUCCEEDED(rv)) {
+      rv = confDir->AppendNative(NS_LITERAL_CSTRING(".pangorc"));
+      if (NS_SUCCEEDED(rv)) {
+        nsAutoCString tmpPath;
+        rv = confDir->GetNativePath(tmpPath);
+        if (NS_SUCCEEDED(rv)) {
+          policy->AddPath(rdonly, tmpPath.get());
+        }
+      }
+    }
+  }
+
+  // Firefox binary dir.
+  // Note that unlike the previous cases, we use NS_GetSpecialDirectory
+  // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
+  // system, which may not be the case for some tests. For quering for the
+  // location of XPCOM things, we can use it anyway.
+  nsCOMPtr<nsIFile> ffDir;
+  rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
+  if (NS_SUCCEEDED(rv)) {
+    nsAutoCString tmpPath;
+    rv = ffDir->GetNativePath(tmpPath);
+    if (NS_SUCCEEDED(rv)) {
+      policy->AddDir(rdonly, tmpPath.get());
+    }
+  }
+
+  if (mozilla::IsDevelopmentBuild()) {
+    // If this is a developer build the resources are symlinks to outside the binary dir.
+    // Therefore in non-release builds we allow reads from the whole repository.
+    // MOZ_DEVELOPER_REPO_DIR is set by mach run.
+    const char *developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
+    if (developer_repo_dir) {
+      policy->AddDir(rdonly, developer_repo_dir);
+    }
+  }
+
   mCommonContentPolicy.reset(policy);
 #endif
 }
 
 #ifdef MOZ_CONTENT_SANDBOX
 UniquePtr<SandboxBroker::Policy>
-SandboxBrokerPolicyFactory::GetContentPolicy(int aPid)
+SandboxBrokerPolicyFactory::GetContentPolicy(int aPid, bool aFileProcess)
 {
   // Policy entries that vary per-process (currently the only reason
   // that can happen is because they contain the pid) are added here.
 
   MOZ_ASSERT(NS_IsMainThread());
   // File broker usage is controlled through a pref.
   if (GetEffectiveContentSandboxLevel() <= 1) {
     return nullptr;
   }
 
   MOZ_ASSERT(mCommonContentPolicy);
   UniquePtr<SandboxBroker::Policy>
     policy(new SandboxBroker::Policy(*mCommonContentPolicy));