Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 30 Mar 2017 15:20:22 -0400
changeset 398672 8df9fabf2587b7020889755acb9e75b664fe13cf
parent 398667 0156a91a9fbab5222c30a274e6d3477caf610074 (current diff)
parent 398671 9a843c3e25dba462d925b71fd40344d53acaa9b0 (diff)
child 398673 0fea861352633dba7bb3223be05107561413ce2b
child 398773 23e4fd04033bb8afe7bf3e63aa5053c9c094b62f
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
8df9fabf2587 / 55.0a1 / 20170331102157 / files
nightly linux64
8df9fabf2587 / 55.0a1 / 20170331102157 / files
nightly mac
8df9fabf2587 / 55.0a1 / 20170331030216 / files
nightly win32
8df9fabf2587 / 55.0a1 / 20170331030216 / files
nightly win64
8df9fabf2587 / 55.0a1 / 20170331030216 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c. a=merge CLOSED TREE
--- a/browser/components/sessionstore/SessionCookies.jsm
+++ b/browser/components/sessionstore/SessionCookies.jsm
@@ -56,27 +56,22 @@ var SessionCookiesInternal = {
    *        [{ tabs: [...], cookies: [...] }, ...]
    */
   update(windows) {
     this._ensureInitialized();
 
     for (let window of windows) {
       let cookies = [];
 
-      // Collect all hosts for the current window.
-      let hosts = this.getHostsForWindow(window, true);
-
-      for (let host of Object.keys(hosts)) {
-        let isPinned = hosts[host];
-
+      // Collect all cookies for the current window.
+      for (let host of this.getHostsForWindow(window, true)) {
         for (let cookie of CookieStore.getCookiesForHost(host)) {
-          // _getCookiesForHost() will only return hosts with the right privacy
-          // rules, so there is no need to do anything special with this call
-          // to PrivacyLevel.canSave().
-          if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned})) {
+          // getCookiesForHost() will only return hosts with the right privacy
+          // rules. Check again here to exclude HTTPS-only cookies if needed.
+          if (PrivacyLevel.canSave(cookie.secure)) {
             cookies.push(cookie);
           }
         }
       }
 
       // Don't include/keep empty cookie sections.
       if (cookies.length) {
         window.cookies = cookies;
@@ -89,27 +84,24 @@ var SessionCookiesInternal = {
   /**
    * Returns a map of all hosts for a given window that we might want to
    * collect cookies for.
    *
    * @param window
    *        A window state object containing tabs with history entries.
    * @param checkPrivacy (bool)
    *        Whether to check the privacy level for each host.
-   * @return {object} A map of hosts for a given window state object. The keys
-   *                  will be hosts, the values are boolean and determine
-   *                  whether we will use the deferred privacy level when
-   *                  checking how much data to save on quitting.
+   * @return {set} A set of hosts for a given window state object.
    */
   getHostsForWindow(window, checkPrivacy = false) {
-    let hosts = {};
+    let hosts = new Set();
 
     for (let tab of window.tabs) {
       for (let entry of tab.entries) {
-        this._extractHostsFromEntry(entry, hosts, checkPrivacy, tab.pinned);
+        this._extractHostsFromEntry(entry, hosts, checkPrivacy);
       }
     }
 
     return hosts;
   },
 
   /**
    * Restores a given list of session cookies.
@@ -173,78 +165,42 @@ var SessionCookiesInternal = {
 
   /**
    * Fill a given map with hosts found in the given entry's session history and
    * any child entries.
    *
    * @param entry
    *        the history entry, serialized
    * @param hosts
-   *        the hash that will be used to store hosts eg, { hostname: true }
+   *        the set that will be used to store hosts
    * @param checkPrivacy
    *        should we check the privacy level for https
-   * @param isPinned
-   *        is the entry we're evaluating for a pinned tab; used only if
-   *        checkPrivacy
    */
-  _extractHostsFromEntry(entry, hosts, checkPrivacy, isPinned) {
-    let host = entry._host;
-    let scheme = entry._scheme;
+  _extractHostsFromEntry(entry, hosts, checkPrivacy) {
+    try {
+      // It's alright if this throws for about: URIs.
+      let {host, scheme} = Utils.makeURI(entry.url);
 
-    // If host & scheme aren't defined, then we are likely here in the startup
-    // process via _splitCookiesFromWindow. In that case, we'll turn entry.url
-    // into an nsIURI and get host/scheme from that. This will throw for about:
-    // urls in which case we don't need to do anything.
-    if (!host && !scheme) {
-      try {
-        let uri = Utils.makeURI(entry.url);
-        host = uri.host;
-        scheme = uri.scheme;
-        this._extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned);
-      } catch (ex) { }
-    }
+      if (scheme == "file") {
+        hosts.add(host);
+      } else if (/https?/.test(scheme)) {
+        if (!checkPrivacy || PrivacyLevel.canSave(scheme == "https")) {
+          hosts.add(host);
+        }
+      }
+    } catch (ex) { }
 
     if (entry.children) {
       for (let child of entry.children) {
-        this._extractHostsFromEntry(child, hosts, checkPrivacy, isPinned);
+        this._extractHostsFromEntry(child, hosts, checkPrivacy);
       }
     }
   },
 
   /**
-   * Add a given host to a given map of hosts if the privacy level allows
-   * saving cookie data for it.
-   *
-   * @param host
-   *        the host of a uri (usually via nsIURI.host)
-   * @param scheme
-   *        the scheme of a uri (usually via nsIURI.scheme)
-   * @param hosts
-   *        the hash that will be used to store hosts eg, { hostname: true }
-   * @param checkPrivacy
-   *        should we check the privacy level for https
-   * @param isPinned
-   *        is the entry we're evaluating for a pinned tab; used only if
-   *        checkPrivacy
-   */
-  _extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned) {
-    // host and scheme may not be set (for about: urls for example), in which
-    // case testing scheme will be sufficient.
-    if (/https?/.test(scheme) && !hosts[host] &&
-        (!checkPrivacy ||
-         PrivacyLevel.canSave({isHttps: scheme == "https", isPinned}))) {
-      // By setting this to true or false, we can determine when looking at
-      // the host in update() if we should check for privacy.
-      hosts[host] = isPinned;
-    } else if (scheme == "file") {
-      hosts[host] = true;
-    }
-  },
-
-  /**
    * Updates or adds a given cookie to the store.
    */
   _updateCookie(cookie) {
     cookie.QueryInterface(Ci.nsICookie2);
 
     if (cookie.isSession) {
       CookieStore.set(cookie);
     } else {
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -4370,17 +4370,17 @@ var SessionStoreInternal = {
     if (!aWinState.cookies || !aWinState.cookies.length)
       return;
 
     // Get the hosts for history entries in aTargetWinState
     let cookieHosts = SessionCookies.getHostsForWindow(aTargetWinState);
 
     // By creating a regex we reduce overhead and there is only one loop pass
     // through either array (cookieHosts and aWinState.cookies).
-    let hosts = Object.keys(cookieHosts).join("|").replace(/\./g, "\\.");
+    let hosts = [...cookieHosts].join("|").replace(/\./g, "\\.");
     // If we don't actually have any hosts, then we don't want to do anything.
     if (!hosts.length)
       return;
     let cookieRegex = new RegExp(".*(" + hosts + ")");
     for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
       if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
         aTargetWinState.cookies =
           aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -379,16 +379,17 @@ TextPropertyEditor.prototype = {
           onShow: this._onStartEditing,
           onPreview: this._onSwatchPreview,
           onCommit: this._onSwatchCommit,
           onRevert: this._onSwatchRevert
         });
         span.on("unit-change", this._onSwatchCommit);
         let title = l10n("rule.colorSwatch.tooltip");
         span.setAttribute("title", title);
+        span.dataset.propertyName = this.nameSpan.textContent;
       }
     }
 
     // Attach the cubic-bezier tooltip to the bezier swatches
     this._bezierSwatchSpans =
       this.valueSpan.querySelectorAll("." + BEZIER_SWATCH_CLASS);
     if (this.ruleEditor.isEditable) {
       for (let span of this._bezierSwatchSpans) {
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -262,16 +262,17 @@ devtools.jar:
     skin/images/security-state-broken.svg (themes/images/security-state-broken.svg)
     skin/images/security-state-insecure.svg (themes/images/security-state-insecure.svg)
     skin/images/security-state-secure.svg (themes/images/security-state-secure.svg)
     skin/images/security-state-weak.svg (themes/images/security-state-weak.svg)
     skin/images/diff.svg (themes/images/diff.svg)
     skin/images/import.svg (themes/images/import.svg)
     skin/images/pane-collapse.svg (themes/images/pane-collapse.svg)
     skin/images/pane-expand.svg (themes/images/pane-expand.svg)
+    skin/images/help.svg (themes/images/help.svg)
 
     # Firebug Theme
     skin/images/firebug/read-only.svg (themes/images/firebug/read-only.svg)
     skin/images/firebug/twisty-closed-firebug.svg (themes/images/firebug/twisty-closed-firebug.svg)
     skin/images/firebug/twisty-open-firebug.svg (themes/images/firebug/twisty-open-firebug.svg)
     skin/images/firebug/arrow-down.svg (themes/images/firebug/arrow-down.svg)
     skin/images/firebug/arrow-up.svg (themes/images/firebug/arrow-up.svg)
     skin/images/firebug/close.svg (themes/images/firebug/close.svg)
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -349,16 +349,50 @@ inspector.sidebar.layoutViewTitle2=Layou
 # This is the title shown in a tab in the side panel of the Inspector panel
 # that corresponds to the tool displaying animations defined in the page.
 inspector.sidebar.animationInspectorTitle=Animations
 
 # LOCALIZATION NOTE (inspector.eyedropper.label): A string displayed as the tooltip of
 # a button in the inspector which toggles the Eyedropper tool
 inspector.eyedropper.label=Grab a color from the page
 
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.header):
+# This string is used as a header to indicate the contrast section of the
+# color widget.
+inspector.colorwidget.contrastRatio.header=Contrast Ratio
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.invalidColor):
+# This string is used when an invalid color is passed as a background color
+# to the color widget.
+inspector.colorwidget.contrastRatio.invalidColor=Invalid color
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.info):
+# This string is used to explain the contrast ratio grading system when you hover over the help icon in the contrast info.
+inspector.colorwidget.contrastRatio.info=The contrast ratio grading system for text has the following grading: Fail, AA*, AAA* AAA from lowest to highest readability.\nIt was calculated based on the computed background color:
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.failGrade):
+# This string is used to indicate that the text fails for contrast ratio grading criteria.
+inspector.colorwidget.contrastRatio.failGrade=Fail
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.failInfo):
+# This string is used to explain that the text fails for contrast ratio grading criteria.
+inspector.colorwidget.contrastRatio.failInfo=This contrast ratio fails for all text sizes.
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.AABigInfo):
+# This string is used to explain that the text passes AA* grade for contrast ratio.
+inspector.colorwidget.contrastRatio.AABigInfo=This contrast ratio passes the AA grade for big text (at least 18 point or 14 point bold sized text).
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.AAABigInfo):
+# This string is used to explain that the text passes the AA grade and AAA* for contrast ratio.
+inspector.colorwidget.contrastRatio.AAABigInfo=This contrast ratio passes the AA grade for all text and AAA grade for big text (at least 18 point or 14 point bold sized text).
+
+# LOCALIZATION NOTE (inspector.colorwidget.contrastRatio.AAAInfo):
+# This string is used to explain that the text passes AAA grade for contrast ratio.
+inspector.colorwidget.contrastRatio.AAAInfo=This contrast ratio passes the AAA grade for all text sizes.
+
 # LOCALIZATION NOTE (inspector.breadcrumbs.label): A string visible only to a screen reader and
 # is used to label (using aria-label attribute) a container for inspector breadcrumbs
 inspector.breadcrumbs.label=Breadcrumbs
 
 # LOCALIZATION NOTE (inspector.browserStyles.label): This is the label for the checkbox
 # that specifies whether the styles that are not from the user's stylesheet should be
 # displayed or not.
 inspector.browserStyles.label=Browser styles
--- a/devtools/client/shared/widgets/ColorWidget.js
+++ b/devtools/client/shared/widgets/ColorWidget.js
@@ -4,20 +4,24 @@
 
 /**
  * This file is a new working copy of Spectrum.js for the purposes of refreshing the color
  * widget. It is hidden behind a pref("devtools.inspector.colorWidget.enabled").
  */
 
 "use strict";
 
+const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {colorUtils} = require("devtools/shared/css/color");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const SAMPLE_TEXT = "Abc";
 
 /**
  * ColorWidget creates a color picker widget in any container you give it.
  *
  * Simple usage example:
  *
  * const {ColorWidget} = require("devtools/client/shared/widgets/ColorWidget");
  * let s = new ColorWidget(containerElement, [255, 126, 255, 1]);
@@ -45,27 +49,75 @@ function ColorWidget(parentEl, rgb) {
   this.onAlphaSliderMove = this.onAlphaSliderMove.bind(this);
   this.onElementClick = this.onElementClick.bind(this);
   this.onDraggerMove = this.onDraggerMove.bind(this);
   this.onHexInputChange = this.onHexInputChange.bind(this);
   this.onHslaInputChange = this.onHslaInputChange.bind(this);
   this.onRgbaInputChange = this.onRgbaInputChange.bind(this);
   this.onSelectValueChange = this.onSelectValueChange.bind(this);
   this.onSliderMove = this.onSliderMove.bind(this);
+  this.updateContrast = this.updateContrast.bind(this);
 
   this.initializeColorWidget();
 
   if (rgb) {
     this.rgb = rgb;
     this.updateUI();
   }
 }
 
 module.exports.ColorWidget = ColorWidget;
 
+/**
+ * Calculates the contrast grade and title for the given contrast
+ * ratio and background color.
+ * @param {Number} contrastRatio Contrast ratio to calculate grade.
+ * @param {String} backgroundColor A string of the form `rgba(r, g, b, a)`
+ * where r, g, b and a are floats.
+ * @return {Object} An object of the form {grade, title}.
+ * |grade| is a string containing the contrast grade.
+ * |title| is a string containing the title of the colorwidget.
+ */
+ColorWidget.calculateGradeAndTitle = function (contrastRatio, backgroundColor) {
+  let grade = "";
+  let title = "";
+
+  if (contrastRatio < 3.0) {
+    grade = L10N.getStr("inspector.colorwidget.contrastRatio.failGrade");
+    title = L10N.getStr("inspector.colorwidget.contrastRatio.failInfo");
+  } else if (contrastRatio < 4.5) {
+    grade = "AA*";
+    title = L10N.getStr("inspector.colorwidget.contrastRatio.AABigInfo");
+  } else if (contrastRatio < 7.0) {
+    grade = "AAA*";
+    title = L10N.getStr("inspector.colorwidget.contrastRatio.AAABigInfo");
+  } else if (contrastRatio < 22.0) {
+    grade = "AAA";
+    title = L10N.getStr("inspector.colorwidget.contrastRatio.AAAInfo");
+  }
+  title += "\n";
+  title += L10N.getStr("inspector.colorwidget.contrastRatio.info") + " ";
+  title += backgroundColor;
+
+  return { grade, title };
+};
+
+/**
+ * Converts the contrastRatio to a string of length 4 by rounding
+ * contrastRatio and padding the required number of 0s before or
+ * after.
+ * @param {Number} contrastRatio The contrast ratio to be formatted.
+ * @return {String} The formatted ratio.
+ */
+ColorWidget.ratioToString = function (contrastRatio) {
+  let formattedRatio = (contrastRatio < 10) ? "0" : "";
+  formattedRatio += contrastRatio.toFixed(2);
+  return formattedRatio;
+};
+
 ColorWidget.hsvToRgb = function (h, s, v, a) {
   let r, g, b;
 
   let i = Math.floor(h * 6);
   let f = h * 6 - i;
   let p = v * (1 - s);
   let q = v * (1 - f * s);
   let t = v * (1 - (1 - f) * s);
@@ -250,22 +302,48 @@ ColorWidget.prototype = {
         </div>
         <div class="colorwidget-hsla colorwidget-hidden">
           <input class="colorwidget-hsla-h" data-id="h" />
           <input class="colorwidget-hsla-s" data-id="s" />
           <input class="colorwidget-hsla-l" data-id="l" />
           <input class="colorwidget-hsla-a" data-id="a" />
         </div>
       </div>
+    <div class="colorwidget-contrast">
+      <div class="colorwidget-contrast-info"></div>
+      <div class="colorwidget-contrast-inner">
+        <span class="colorwidget-colorswatch"></span>
+        <span class="colorwidget-contrast-ratio"></span>
+        <span class="colorwidget-contrast-grade"></span>
+        <button class="colorwidget-contrast-help devtools-button"></button>
+      </div>
+    </div>
     `;
 
     this.element.addEventListener("click", this.onElementClick);
 
     this.parentEl.appendChild(this.element);
 
+    this.closestBackgroundColor = "rgba(255, 255, 255, 1)";
+
+    this.contrast = this.element.querySelector(".colorwidget-contrast");
+    this.contrastInfo = this.element.querySelector(".colorwidget-contrast-info");
+    this.contrastInfo.textContent = L10N.getStr(
+      "inspector.colorwidget.contrastRatio.header"
+    );
+
+    this.contrastInner = this.element.querySelector(".colorwidget-contrast-inner");
+    this.contrastSwatch = this.contrastInner.querySelector(".colorwidget-colorswatch");
+
+    this.contrastSwatch.textContent = SAMPLE_TEXT;
+
+    this.contrastRatio = this.contrastInner.querySelector(".colorwidget-contrast-ratio");
+    this.contrastGrade = this.contrastInner.querySelector(".colorwidget-contrast-grade");
+    this.contrastHelp = this.contrastInner.querySelector(".colorwidget-contrast-help");
+
     this.slider = this.element.querySelector(".colorwidget-hue");
     this.slideHelper = this.element.querySelector(".colorwidget-slider");
     ColorWidget.draggable(this.slider, this.onSliderMove);
 
     this.dragger = this.element.querySelector(".colorwidget-color");
     this.dragHelper = this.element.querySelector(".colorwidget-dragger");
     ColorWidget.draggable(this.dragger, this.onDraggerMove);
 
@@ -295,30 +373,36 @@ ColorWidget.prototype = {
       h: this.element.querySelector(".colorwidget-hsla-h"),
       s: this.element.querySelector(".colorwidget-hsla-s"),
       l: this.element.querySelector(".colorwidget-hsla-l"),
       a: this.element.querySelector(".colorwidget-hsla-a"),
     };
     this.hslaValue.addEventListener("input", this.onHslaInputChange);
   },
 
-  show: function () {
+  show: Task.async(function* () {
     this.initializeColorWidget();
     this.element.classList.add("colorwidget-show");
 
     this.slideHeight = this.slider.offsetHeight;
     this.dragWidth = this.dragger.offsetWidth;
     this.dragHeight = this.dragger.offsetHeight;
     this.dragHelperHeight = this.dragHelper.offsetHeight;
     this.slideHelperHeight = this.slideHelper.offsetHeight;
     this.alphaSliderWidth = this.alphaSliderInner.offsetWidth;
     this.alphaSliderHelperWidth = this.alphaSliderHelper.offsetWidth;
 
+    if (this.inspector && this.inspector.selection.nodeFront && this.contrastEnabled) {
+      let node = this.inspector.selection.nodeFront;
+      this.closestBackgroundColor = yield node.getClosestBackgroundColor();
+    }
+    this.updateContrast();
+
     this.updateUI();
-  },
+  }),
 
   onElementClick: function (e) {
     e.stopPropagation();
   },
 
   onSliderMove: function (dragX, dragY) {
     this.hsv[0] = (dragY / this.slideHeight);
     this.hsl[0] = (dragY / this.slideHeight) * 360;
@@ -453,19 +537,60 @@ ColorWidget.prototype = {
 
     this.hsl = hsl;
 
     this.updateUI();
     this.onChange();
   },
 
   onChange: function () {
+    this.updateContrast();
     this.emit("changed", this.rgb, this.rgbCssString);
   },
 
+  updateContrast: function () {
+    if (!this.contrastEnabled) {
+      this.contrast.style.display = "none";
+      return;
+    }
+
+    this.contrast.style.display = "initial";
+
+    if (!colorUtils.isValidCSSColor(this.closestBackgroundColor)) {
+      this.contrastRatio.textContent = L10N.getStr(
+        "inspector.colorwidget.contrastRatio.invalidColor"
+      );
+
+      this.contrastGrade.textContent = "";
+      this.contrastHelp.removeAttribute("title");
+      return;
+    }
+    if (!this.rgbaColor) {
+      this.rgbaColor = new colorUtils.CssColor(this.closestBackgroundColor);
+    }
+    this.rgbaColor.newColor(this.closestBackgroundColor);
+    let rgba = this.rgbaColor._getRGBATuple();
+    let backgroundColor = [rgba.r, rgba.g, rgba.b, rgba.a];
+
+    let textColor = this.rgb;
+
+    let ratio = colorUtils.calculateContrastRatio(backgroundColor, textColor);
+
+    let contrastDetails = ColorWidget.calculateGradeAndTitle(ratio,
+                        this.rgbaColor.toString());
+
+    this.contrastRatio.textContent = ColorWidget.ratioToString(ratio);
+    this.contrastGrade.textContent = contrastDetails.grade;
+
+    this.contrastHelp.setAttribute("title", contrastDetails.title);
+
+    this.contrastSwatch.style.backgroundColor = this.rgbaColor.toString();
+    this.contrastSwatch.style.color = this.rgbCssString;
+  },
+
   updateHelperLocations: function () {
     // If the UI hasn't been shown yet then none of the dimensions will be
     // correct
     if (!this.element.classList.contains("colorwidget-show")) {
       return;
     }
 
     let h = this.hsv[0];
--- a/devtools/client/shared/widgets/color-widget.css
+++ b/devtools/client/shared/widgets/color-widget.css
@@ -30,16 +30,22 @@
 }
 
 .colorwidget-box {
   border: 1px solid rgba(0,0,0,0.2);
   border-radius: 2px;
   background-clip: content-box;
 }
 
+.colorwidget-colorswatch {
+  background-color: transparent;
+  color: transparent;
+  border: 1px solid transparent;
+}
+
 /* Elements */
 
 #colorwidget-tooltip {
   padding: 4px;
 }
 
 .colorwidget-container {
   position: relative;
@@ -66,16 +72,53 @@ http://www.briangrinstead.com/blog/keep-
 .colorwidget-top-inner {
   position: absolute;
   top:0;
   left:0;
   bottom:0;
   right:0;
 }
 
+.colorwidget-contrast {
+  color: var(--theme-content-color1);
+  padding-top: 4px;
+}
+
+.colorwidget-colorswatch, .colorwidget-contrast-ratio, .colorwidget-contrast-grade, .colorwidget-contrast-help {
+  display: inline-block;
+}
+
+.colorwidget-colorswatch {
+  width: 28%;
+}
+
+.colorwidget-contrast-ratio {
+  font-family: Courier New, Courier, monospace;
+  padding-left: 8px;
+  width: 26%;
+}
+
+.colorwidget-contrast-grade {
+  font-family: Courier New, Courier, monospace;
+  width: 18%;
+}
+
+.colorwidget-contrast-help {
+  margin-inline-start: 5px;
+}
+
+.colorwidget-contrast-help::before {
+  background-image: url(chrome://devtools/skin/images/help.svg);
+}
+
+.colorwidget-colorswatch {
+  text-align: center;
+  color: transparent;
+}
+
 .colorwidget-color {
   position: absolute;
   top: 0;
   left: 0;
   bottom: 0;
   right: 20%;
 }
 
--- a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -67,56 +67,72 @@ SwatchColorPickerTooltip.prototype = Her
 
     let widget;
     let node = doc.createElementNS(XHTML_NS, "div");
 
     if (NEW_COLOR_WIDGET) {
       node.id = "colorwidget";
       container.appendChild(node);
       widget = new ColorWidget(node, color);
+      this.tooltip.setContent(container, { width: 218, height: 320 });
     } else {
       node.id = "spectrum";
       container.appendChild(node);
       widget = new Spectrum(node, color);
+      this.tooltip.setContent(container, { width: 218, height: 224 });
     }
+    widget.inspector = this.inspector;
 
     let eyedropper = doc.createElementNS(XHTML_NS, "button");
     eyedropper.id = "eyedropper-button";
     eyedropper.className = "devtools-button";
     /* pointerEvents for eyedropper has to be set auto to display tooltip when
      * eyedropper is disabled in non-HTML documents.
      */
     eyedropper.style.pointerEvents = "auto";
     container.appendChild(eyedropper);
 
-    this.tooltip.setContent(container, { width: 218, height: 224 });
-
     // Wait for the tooltip to be shown before calling widget.show
     // as it expect to be visible in order to compute DOM element sizes.
     this.tooltip.once("shown", () => {
       widget.show();
     });
 
     return widget;
   },
 
   /**
    * Overriding the SwatchBasedEditorTooltip.show function to set spectrum's
    * color.
    */
   show: Task.async(function* () {
+    // set contrast enabled for the spectrum
+    let name = this.activeSwatch.dataset.propertyName;
+
+    if (this.isContrastCompatible === undefined) {
+      let target = this.inspector.target;
+      this.isContrastCompatible = yield target.actorHasMethod(
+        "domnode",
+        "getClosestBackgroundColor"
+      );
+    }
+
+    // only enable contrast if it is compatible and if the type of property is color.
+    this.spectrum.contrastEnabled = (name === "color") && this.isContrastCompatible;
+
     // Call then parent class' show function
     yield SwatchBasedEditorTooltip.prototype.show.call(this);
 
     // Then set spectrum's color and listen to color changes to preview them
     if (this.activeSwatch) {
       this.currentSwatchColor = this.activeSwatch.nextSibling;
       this._originalColor = this.currentSwatchColor.textContent;
       let color = this.activeSwatch.style.backgroundColor;
       this.spectrum.off("changed", this._onSpectrumColorChange);
+
       this.spectrum.rgb = this._colorToRgba(color);
       this.spectrum.on("changed", this._onSpectrumColorChange);
       this.spectrum.updateUI();
     }
 
     let eyeButton = this.tooltip.container.querySelector("#eyedropper-button");
     let canShowEyeDropper = yield this.inspector.supportsEyeDropper();
     if (canShowEyeDropper) {
@@ -159,17 +175,16 @@ SwatchColorPickerTooltip.prototype = Her
       this.hide();
 
       this.tooltip.emit("eyedropper-opened");
     }, e => console.error(e));
 
     inspector.once("color-picked", color => {
       toolbox.win.focus();
       this._selectColor(color);
-      this._onEyeDropperDone();
     });
 
     inspector.once("color-pick-canceled", () => {
       this._onEyeDropperDone();
     });
   },
 
   _onEyeDropperDone: function () {
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/help.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+  <circle cx="12" cy="12" r="11" stroke-width="2" stroke="currentColor" fill="none"/>
+  <path d="M12.2,4.9c-1.6,0-2.9,0.4-3.8,0.8L9.2,8c0.6-0.4,1.5-0.6,2.2-0.6c1.1,0,1.6,0.5,1.6,1.2 c0,0.7-0.6,1.3-1.3,2.1c-1,1.1-1.4,2.1-1.3,3.2l0,0.5h3V14c0-0.9,0.3-1.7,1.2-2.5c0.9-0.9,1.9-1.9,1.9-3.4 C16.6,6.4,15.2,4.9,12.2,4.9z M12,16.1c-1.1,0-1.9,0.8-1.9,1.9c0,1.1,0.8,1.9,1.9,1.9c1.2,0,1.9-0.8,1.9-1.9 C13.9,16.9,13.1,16.1,12,16.1z"/>
+</svg>
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -71,16 +71,17 @@ const {
   isAnonymous,
   isNativeAnonymous,
   isXBLAnonymous,
   isShadowAnonymous,
   getFrameElement
 } = require("devtools/shared/layout/utils");
 const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/reflow");
 const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
+const {colorUtils} = require("devtools/shared/css/color");
 
 const {EventParsers} = require("devtools/server/event-parsers");
 const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
@@ -745,16 +746,39 @@ var NodeActor = exports.NodeActor = prot
     let options = {
       previewText: FONT_FAMILY_PREVIEW_TEXT,
       previewFontSize: FONT_FAMILY_PREVIEW_TEXT_SIZE,
       fillStyle: fillStyle
     };
     let { dataURL, size } = getFontPreviewData(font, doc, options);
 
     return { data: LongStringActor(this.conn, dataURL), size: size };
+  },
+
+  /**
+   * Finds the computed background color of the closest parent with
+   * a set background color.
+   * Returns a string with the background color of the form
+   * rgba(r, g, b, a). Defaults to rgba(255, 255, 255, 1) if no
+   * background color is found.
+   */
+  getClosestBackgroundColor: function () {
+    let current = this.rawNode;
+    while (current) {
+      let computedStyle = CssLogic.getComputedStyle(current);
+      let currentStyle = computedStyle.getPropertyValue("background-color");
+      if (colorUtils.isValidCSSColor(currentStyle)) {
+        let currentCssColor = new colorUtils.CssColor(currentStyle);
+        if (!currentCssColor.isTransparent()) {
+          return currentCssColor.rgba;
+        }
+      }
+      current = current.parentNode;
+    }
+    return "rgba(255, 255, 255, 1)";
   }
 });
 
 /**
  * Server side of a node list as returned by querySelectorAll()
  */
 var NodeListActor = exports.NodeListActor = protocol.ActorClassWithSpec(nodeListSpec, {
   typeName: "domnodelist",
--- a/devtools/shared/css/color.js
+++ b/devtools/shared/css/color.js
@@ -70,16 +70,17 @@ function CssColor(colorValue, supportsCs
 module.exports.colorUtils = {
   CssColor: CssColor,
   rgbToHsl: rgbToHsl,
   setAlpha: setAlpha,
   classifyColor: classifyColor,
   rgbToColorName: rgbToColorName,
   colorToRGBA: colorToRGBA,
   isValidCSSColor: isValidCSSColor,
+  calculateContrastRatio: calculateContrastRatio,
 };
 
 /**
  * Values used in COLOR_UNIT_PREF
  */
 CssColor.COLORUNIT = {
   "authored": "authored",
   "hex": "hex",
@@ -440,16 +441,25 @@ CssColor.prototype = {
   },
 
   /**
    * This method allows comparison of CssColor objects using ===.
    */
   valueOf: function () {
     return this.rgba;
   },
+
+  /**
+   * Check whether the color is fully transparent (alpha === 0).
+   *
+   * @return {Boolean} True if the color is transparent and valid.
+   */
+  isTransparent: function () {
+    return this._getRGBATuple().a === 0;
+  },
 };
 
 /**
  * Convert rgb value to hsl
  *
  * @param {array} rgb
  *         Array of rgb values
  * @return {array}
@@ -1137,8 +1147,42 @@ function colorToRGBA(name, useCssColor4C
  *
  * @param {String} name The string to check
  * @param {Boolean} useCssColor4ColorFunction use css-color-4 color function or not.
  * @return {Boolean} True if the string is a CSS color name.
  */
 function isValidCSSColor(name, useCssColor4ColorFunction = false) {
   return colorToRGBA(name, useCssColor4ColorFunction) !== null;
 }
+
+/**
+ * Calculates the luminance of a rgba tuple based on the formula given in
+ * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+ *
+ * @param {Array} rgba An array with [r,g,b,a] values.
+ * @return {Number} The calculated luminance.
+ */
+function calculateLuminance(rgba) {
+  for (let i = 0; i < 3; i++) {
+    rgba[i] /= 255;
+    rgba[i] = (rgba[i] < 0.03928) ? (rgba[i] / 12.92) :
+                                    Math.pow(((rgba[i] + 0.055) / 1.055), 2.4);
+  }
+  return 0.2126 * rgba[0] + 0.7152 * rgba[1] + 0.0722 * rgba[2];
+}
+
+/**
+ * Calculates the contrast ratio of 2 rgba tuples based on the formula in
+ * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast7
+ *
+ * @param {Array} backgroundColor An array with [r,g,b,a] values containing
+ * the background color.
+ * @param {Array} textColor An array with [r,g,b,a] values containing
+ * the text color.
+ * @return {Number} The calculated luminance.
+ */
+function calculateContrastRatio(backgroundColor, textColor) {
+  let backgroundLuminance = calculateLuminance(backgroundColor);
+  let textLuminance = calculateLuminance(textColor);
+  let ratio = (textLuminance + 0.05) / (backgroundLuminance + 0.05);
+
+  return (ratio > 1.0) ? ratio : (1 / ratio);
+}
--- a/devtools/shared/specs/node.js
+++ b/devtools/shared/specs/node.js
@@ -61,13 +61,19 @@ const nodeSpec = generateActorSpec({
       request: {
         modifications: Arg(0, "array:json")
       },
       response: {}
     },
     getFontFamilyDataURL: {
       request: {font: Arg(0, "string"), fillStyle: Arg(1, "nullable:string")},
       response: RetVal("imageData")
-    }
+    },
+    getClosestBackgroundColor: {
+      request: {},
+      response: {
+        value: RetVal("string")
+      }
+    },
   }
 });
 
 exports.nodeSpec = nodeSpec;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1908,18 +1908,16 @@ Navigator::GetUserAgent(nsPIDOMWindowInn
   }
 
   CopyASCIItoUTF16(ua, aUserAgent);
 
   if (!aWindow) {
     return NS_OK;
   }
 
-  MOZ_ASSERT(aWindow->GetDocShell());
-
   // Copy the User-Agent header from the document channel which has already been
   // subject to UA overrides.
   nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
   if (!doc) {
     return NS_OK;
   }
   nsCOMPtr<nsIHttpChannel> httpChannel =
     do_QueryInterface(doc->GetChannel());
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bailout-env.js
@@ -0,0 +1,54 @@
+function inner_env() {
+    let result = [];
+
+    let x = 0;
+    result.push(() => x);
+
+    var tmp = [1,2,3];
+    for (let y in tmp)
+        result.push(() => tmp[y])
+
+    for (let z = 4; z < 7; z++)
+        result.push(() => z)
+
+    return result;
+}
+
+function outer_env() {
+    let result = inner_env();
+
+    var tmp = [7,8,9];
+    for (let x in tmp)
+        result.push(() => tmp[x])
+
+    return result;
+}
+
+function check_result(result, expectedLen) {
+    assertEq(result.length, expectedLen);
+
+    for (var i = 0; i < expectedLen; ++i)
+        assertEq(result[i], i);
+}
+
+// Wipeout jitcode
+bailout();
+gc(); gc();
+
+// Test lexical environment bailouts
+for (var i = 0; i < 100; ++i)
+{
+    bailAfter(i);
+
+    var result = inner_env().map(fn => fn());
+    check_result(result, 7);
+}
+
+// Test inlined lexical environment bailouts
+for (var i = 0; i < 100; ++i)
+{
+    bailAfter(i);
+
+    var result = outer_env().map(fn => fn());
+    check_result(result, 10);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bailout-spread.js
@@ -0,0 +1,53 @@
+function f1(a,b,c,d) {
+    if (a < 0)
+        throw arguments;
+
+    return a + b + c + d;
+}
+
+function f2(a,b,c,d,e) {
+    return f1(a,b,c,d*e);
+}
+
+function f3(a,v) {
+    return f2(a, ...v);
+}
+
+function f4(i, j) {
+    return f3(i, [j,3,4,5]);
+}
+
+function f5(i) {
+    return f4(i, i);
+}
+
+// Clean jitcode
+gc(); gc();
+
+// Test bailouts through spreadcall
+for (var i = 0; i < 1000; ++i)
+{
+    bailAfter(i);
+    assertEq(f5(i), i+i+23);
+}
+
+// Test exception unwind bailout through spreadcall
+for (var i = 1; i < 100; ++i)
+{
+    let x;
+
+    try {
+        f5(-i);
+
+        // Unreachable
+        assertEq(1, 0);
+    }
+    catch (e) {
+        x = e;
+    }
+
+    assertEq(x[0], -i);
+    assertEq(x[1], -i);
+    assertEq(x[2],  3);
+    assertEq(x[3], 20);
+}
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -468,18 +468,18 @@ IsInlinableFallback(ICFallbackStub* icEn
 static inline void*
 GetStubReturnAddress(JSContext* cx, jsbytecode* pc)
 {
     if (IsGetPropPC(pc))
         return cx->compartment()->jitCompartment()->baselineGetPropReturnAddr();
     if (IsSetPropPC(pc))
         return cx->compartment()->jitCompartment()->baselineSetPropReturnAddr();
     // This should be a call op of some kind, now.
-    MOZ_ASSERT(IsCallPC(pc));
-    return cx->compartment()->jitCompartment()->baselineCallReturnAddr(JSOp(*pc) == JSOP_NEW);
+    MOZ_ASSERT(IsCallPC(pc) && !IsSpreadCallPC(pc));
+    return cx->compartment()->jitCompartment()->baselineCallReturnAddr(IsConstructorCallPC(pc));
 }
 
 static inline jsbytecode*
 GetNextNonLoopEntryPc(jsbytecode* pc)
 {
     JSOp op = JSOp(*pc);
     if (op == JSOP_GOTO)
         return pc + GET_JUMP_OFFSET(pc);
@@ -862,30 +862,35 @@ InitFromBailout(JSContext* cx, HandleScr
     // FirstExecution bailouts, we invalidate and recompile the script with
     // IonMonkey. Failing to increment the counter of the current basic block
     // might lead to repeated bailouts and invalidations.
     if (!JitOptions.disablePgo && script->hasScriptCounts())
         script->incHitCount(pc);
 
     JSOp op = JSOp(*pc);
 
+    // Inlining of SPREADCALL-like frames not currently supported.
+    MOZ_ASSERT_IF(IsSpreadCallPC(pc), !iter.moreFrames());
+
     // Fixup inlined JSOP_FUNCALL, JSOP_FUNAPPLY, and accessors on the caller side.
     // On the caller side this must represent like the function wasn't inlined.
     uint32_t pushedSlots = 0;
     AutoValueVector savedCallerArgs(cx);
     bool needToSaveArgs = op == JSOP_FUNAPPLY || IsGetPropPC(pc) || IsSetPropPC(pc);
     if (iter.moreFrames() && (op == JSOP_FUNCALL || needToSaveArgs))
     {
         uint32_t inlined_args = 0;
-        if (op == JSOP_FUNCALL)
+        if (op == JSOP_FUNCALL) {
             inlined_args = 2 + GET_ARGC(pc) - 1;
-        else if (op == JSOP_FUNAPPLY)
+        } else if (op == JSOP_FUNAPPLY) {
             inlined_args = 2 + blFrame->numActualArgs();
-        else
+        } else {
+            MOZ_ASSERT(IsGetPropPC(pc) || IsSetPropPC(pc));
             inlined_args = 2 + IsSetPropPC(pc);
+        }
 
         MOZ_ASSERT(exprStackSlots >= inlined_args);
         pushedSlots = exprStackSlots - inlined_args;
 
         JitSpew(JitSpew_BaselineBailouts,
                 "      pushing %u expression stack slots before fixup",
                 pushedSlots);
         for (uint32_t i = 0; i < pushedSlots; i++) {
@@ -1011,17 +1016,16 @@ InitFromBailout(JSContext* cx, HandleScr
             fasterPc = GetNextNonLoopEntryPc(GetNextNonLoopEntryPc(fasterPc));
             if (fasterPc == pc)
                 break;
         }
         op = JSOp(*pc);
     }
 
     uint32_t pcOff = script->pcToOffset(pc);
-    bool isCall = IsCallPC(pc);
     BaselineScript* baselineScript = script->baselineScript();
 
 #ifdef DEBUG
     uint32_t expectedDepth;
     bool reachablePC;
     if (!ReconstructStackDepth(cx, script, resumeAfter ? GetNextPc(pc) : pc, &expectedDepth, &reachablePC))
         return false;
 
@@ -1056,17 +1060,17 @@ InitFromBailout(JSContext* cx, HandleScr
 #ifdef JS_JITSPEW
     JitSpew(JitSpew_BaselineBailouts, "      Resuming %s pc offset %d (op %s) (line %d) of %s:%" PRIuSIZE,
                 resumeAfter ? "after" : "at", (int) pcOff, CodeName[op],
                 PCToLineNumber(script, pc), script->filename(), script->lineno());
     JitSpew(JitSpew_BaselineBailouts, "      Bailout kind: %s",
             BailoutKindString(bailoutKind));
 #endif
 
-    bool pushedNewTarget = op == JSOP_NEW;
+    bool pushedNewTarget = IsConstructorCallPC(pc);
 
     // If this was the last inline frame, or we are bailing out to a catch or
     // finally block in this frame, then unpacking is almost done.
     if (!iter.moreFrames() || catchingException) {
         // Last frame, so PC for call to next frame is set to nullptr.
         *callPC = nullptr;
 
         // If the bailout was a resumeAfter, and the opcode is monitored,
@@ -1078,17 +1082,17 @@ InitFromBailout(JSContext* cx, HandleScr
             // JSOP_NEWOBJECT, which always returns the same type for a
             // particular script/pc location.
             BaselineICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff);
             ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback();
             if (fallbackStub->isMonitoredFallback())
                 enterMonitorChain = true;
         }
 
-        uint32_t numCallArgs = isCall ? GET_ARGC(pc) : 0;
+        uint32_t numUses = js::StackUses(script, pc);
 
         if (resumeAfter && !enterMonitorChain)
             pc = GetNextPc(pc);
 
         builder.setResumePC(pc);
         builder.setResumeFramePtr(prevFramePtr);
 
         if (enterMonitorChain) {
@@ -1109,34 +1113,35 @@ InitFromBailout(JSContext* cx, HandleScr
             blFrame->setFrameSize(frameSize);
             JitSpew(JitSpew_BaselineBailouts, "      Adjusted framesize -= %d: %d",
                             (int) sizeof(Value), (int) frameSize);
 
             // If resuming into a JSOP_CALL, baseline keeps the arguments on the
             // stack and pops them only after returning from the call IC.
             // Push undefs onto the stack in anticipation of the popping of the
             // callee, thisv, and actual arguments passed from the caller's frame.
-            if (isCall) {
+            if (IsCallPC(pc)) {
+                uint32_t numCallArgs = numUses - 2 - uint32_t(pushedNewTarget);
                 if (!builder.writeValue(UndefinedValue(), "CallOp FillerCallee"))
                     return false;
                 if (!builder.writeValue(UndefinedValue(), "CallOp FillerThis"))
                     return false;
                 for (uint32_t i = 0; i < numCallArgs; i++) {
                     if (!builder.writeValue(UndefinedValue(), "CallOp FillerArg"))
                         return false;
                 }
                 if (pushedNewTarget) {
                     if (!builder.writeValue(UndefinedValue(), "CallOp FillerNewTarget"))
                         return false;
                 }
 
-                frameSize += (numCallArgs + 2 + pushedNewTarget) * sizeof(Value);
+                frameSize += numUses * sizeof(Value);
                 blFrame->setFrameSize(frameSize);
                 JitSpew(JitSpew_BaselineBailouts, "      Adjusted framesize += %d: %d",
-                                (int) ((numCallArgs + 2 + pushedNewTarget) * sizeof(Value)),
+                                (int) (numUses * sizeof(Value)),
                                 (int) frameSize);
             }
 
             // Set the resume address to the return point from the IC, and set
             // the monitor stub addr.
             builder.setResumeAddr(baselineScript->returnAddressForIC(icEntry));
             builder.setMonitorStub(firstMonStub);
             JitSpew(JitSpew_BaselineBailouts, "      Set resumeAddr=%p monitorStub=%p",
@@ -1357,17 +1362,17 @@ InitFromBailout(JSContext* cx, HandleScr
     // Push actual argc
     if (!builder.writeWord(actualArgc, "ActualArgc"))
         return false;
 
     // Push callee token (must be a JS Function)
     JitSpew(JitSpew_BaselineBailouts, "      Callee = %016" PRIx64, callee.asRawBits());
 
     JSFunction* calleeFun = &callee.toObject().as<JSFunction>();
-    if (!builder.writePtr(CalleeToToken(calleeFun, JSOp(*pc) == JSOP_NEW), "CalleeToken"))
+    if (!builder.writePtr(CalleeToToken(calleeFun, pushedNewTarget), "CalleeToken"))
         return false;
     nextCallee.set(calleeFun);
 
     // Push BaselineStub frame descriptor
     if (!builder.writeWord(baselineStubFrameDescr, "Descriptor"))
         return false;
 
     // Push return address into ICCall_Scripted stub, immediately after the call.
@@ -1459,17 +1464,17 @@ InitFromBailout(JSContext* cx, HandleScr
                                                      JitFrame_Rectifier,
                                                      JitFrameLayout::Size());
 
     // Push actualArgc
     if (!builder.writeWord(actualArgc, "ActualArgc"))
         return false;
 
     // Push calleeToken again.
-    if (!builder.writePtr(CalleeToToken(calleeFun, JSOp(*pc) == JSOP_NEW), "CalleeToken"))
+    if (!builder.writePtr(CalleeToToken(calleeFun, pushedNewTarget), "CalleeToken"))
         return false;
 
     // Push rectifier frame descriptor
     if (!builder.writeWord(rectifierFrameDescr, "Descriptor"))
         return false;
 
     // Push return address into the ArgumentsRectifier code, immediately after the ioncode
     // call.
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -179,17 +179,19 @@ IsIonEnabled(JSContext* cx)
 #endif
 }
 
 inline bool
 IsIonInlinablePC(jsbytecode* pc) {
     // CALL, FUNCALL, FUNAPPLY, EVAL, NEW (Normal Callsites)
     // GETPROP, CALLPROP, and LENGTH. (Inlined Getters)
     // SETPROP, SETNAME, SETGNAME (Inlined Setters)
-    return IsCallPC(pc) || IsGetPropPC(pc) || IsSetPropPC(pc);
+    return (IsCallPC(pc) && !IsSpreadCallPC(pc)) ||
+           IsGetPropPC(pc) ||
+           IsSetPropPC(pc);
 }
 
 inline bool
 TooManyActualArguments(unsigned nargs)
 {
     return nargs > JitOptions.maxStackArgs;
 }
 
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -2403,17 +2403,17 @@ InlineFrameIterator::findNextFrame()
         } else if (IsSetPropPC(pc_)) {
             numActualArgs_ = 1;
         }
 
         if (numActualArgs_ == 0xbadbad)
             MOZ_CRASH("Couldn't deduce the number of arguments of an ionmonkey frame");
 
         // Skip over non-argument slots, as well as |this|.
-        bool skipNewTarget = JSOp(*pc_) == JSOP_NEW;
+        bool skipNewTarget = IsConstructorCallPC(pc_);
         unsigned skipCount = (si_.numAllocations() - 1) - numActualArgs_ - 1 - skipNewTarget;
         for (unsigned j = 0; j < skipCount; j++)
             si_.skip();
 
         // This value should correspond to the function which is being inlined.
         // The value must be readable to iterate over the inline frame. Most of
         // the time, these functions are stored as JSFunction constants,
         // register which are holding the JSFunction pointer, or recover
@@ -2567,19 +2567,19 @@ InlineFrameIterator::isConstructing() co
         InlineFrameIterator parent(TlsContext.get(), this);
         ++parent;
 
         // Inlined Getters and Setters are never constructing.
         if (IsGetPropPC(parent.pc()) || IsSetPropPC(parent.pc()))
             return false;
 
         // In the case of a JS frame, look up the pc from the snapshot.
-        MOZ_ASSERT(IsCallPC(parent.pc()));
-
-        return (JSOp)*parent.pc() == JSOP_NEW;
+        MOZ_ASSERT(IsCallPC(parent.pc()) && !IsSpreadCallPC(parent.pc()));
+
+        return IsConstructorCallPC(parent.pc());
     }
 
     return frame_->isConstructing();
 }
 
 bool
 JitFrameIterator::isConstructing() const
 {
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -796,16 +796,37 @@ IsCallPC(jsbytecode* pc)
 
 inline bool
 IsStrictEvalPC(jsbytecode* pc)
 {
     JSOp op = JSOp(*pc);
     return op == JSOP_STRICTEVAL || op == JSOP_STRICTSPREADEVAL;
 }
 
+inline bool
+IsConstructorCallPC(jsbytecode* pc)
+{
+    JSOp op = JSOp(*pc);
+    return op == JSOP_NEW ||
+           op == JSOP_SUPERCALL ||
+           op == JSOP_SPREADNEW ||
+           op == JSOP_SPREADSUPERCALL;
+}
+
+inline bool
+IsSpreadCallPC(jsbytecode* pc)
+{
+    JSOp op = JSOp(*pc);
+    return op == JSOP_SPREADCALL ||
+           op == JSOP_SPREADNEW ||
+           op == JSOP_SPREADSUPERCALL ||
+           op == JSOP_SPREADEVAL ||
+           op == JSOP_STRICTSPREADEVAL;
+}
+
 static inline int32_t
 GetBytecodeInteger(jsbytecode* pc)
 {
     switch (JSOp(*pc)) {
       case JSOP_ZERO:   return 0;
       case JSOP_ONE:    return 1;
       case JSOP_UINT16: return GET_UINT16(pc);
       case JSOP_UINT24: return GET_UINT24(pc);
--- a/toolkit/modules/sessionstore/PrivacyLevel.jsm
+++ b/toolkit/modules/sessionstore/PrivacyLevel.jsm
@@ -30,28 +30,26 @@ var PrivacyLevel = Object.freeze({
   /**
    * Returns whether the current privacy level allows saving data for the given
    * |url|.
    *
    * @param url The URL we want to save data for.
    * @return bool
    */
   check(url) {
-    return PrivacyLevel.canSave({ isHttps: url.startsWith("https:") });
+    return PrivacyLevel.canSave(url.startsWith("https:"));
   },
 
   /**
    * Checks whether we're allowed to save data for a specific site.
    *
-   * @param {isHttps: boolean}
-   *        An object that must have one property: 'isHttps'.
-   *        'isHttps' tells whether the site us secure communication (HTTPS).
+   * @param isHttps A boolean that tells whether the site uses TLS.
    * @return {bool} Whether we can save data for the specified site.
    */
-  canSave({isHttps}) {
+  canSave(isHttps) {
     let level = Services.prefs.getIntPref(PREF);
 
     // Never save any data when full privacy is requested.
     if (level == PRIVACY_FULL) {
       return false;
     }
 
     // Don't save data for encrypted sites when requested.