merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 25 Jul 2017 14:27:17 +0200
changeset 421904 07484bfdb96bc7297c404e377eea93f1d8ca4442
parent 421846 80394cbcae0f02da8eb801edd92e56f974e5db4f (current diff)
parent 421903 18b80915d07365596301f88d6a9b4bbb65eb5a93 (diff)
child 421905 ac69b48a3503696c7f26ae70526970f7bde00703
child 421942 8a193ce80a59d7a2062783efdf215c4c36fd8d6f
child 421982 4b4a6f82e3e8e9b4ecee284666eeb397f31aa8fc
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)
reviewersmerge
milestone56.0a1
first release with
nightly linux32
07484bfdb96b / 56.0a1 / 20170725144053 / files
nightly linux64
07484bfdb96b / 56.0a1 / 20170725144053 / files
nightly mac
07484bfdb96b / 56.0a1 / 20170725144001 / files
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
merge mozilla-inbound to mozilla-central a=merge
browser/app/profile/firefox.js
browser/base/content/test/plugins/browser_CTP_data_urls.js
browser/base/content/test/plugins/plugin_data_url.html
browser/base/content/urlbarBindings.xml
browser/extensions/activity-stream/data/content/activity-stream.bundle.js
browser/themes/shared/incontentprefs/preferences.inc.css
dom/base/nsGlobalWindow.cpp
dom/ipc/ContentChild.cpp
layout/painting/nsCSSRendering.cpp
modules/libpref/init/all.js
testing/mochitest/runtests.py
testing/mozharness/configs/unittests/linux_unittest.py
tools/profiler/core/platform.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/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_